Skip to content

[css-nesting-1] nesting limited to conditional styling of the current element (another proposal) #8166

Closed
@romainmenke

Description

@romainmenke

The points by @mirisuzanne about the overlap between @scope and nesting made me consider another possible direction for nesting.

I think the main takeaway here is that we need to keep thinking of these features as intertwined. Specifically, the implementation of scope might change what people need or want from nesting.

All current proposal are based on what is done in preprocessors and try to make some version of that work in a browser context. None of them consider the existence and use cases of @scope.

The feature in preprocessors can be roughly split in these groups :

  1. templating to construct BEM style selectors
  2. grouping styles for different elements but all part of the same component
  3. grouping conditional styles for the same element
  4. side effects of the lax syntax in preprocessors
  5. reduce repetition

1. templating to construct BEM style selectors

Examples :

  • &__element
  • &--modifier

This is not part of any of the proposals because it can not (and should not) be implemented.
Authors however use this a lot.

The underlying need is that @scope doesn't exist (yet) and that BEM and friends are popular ways to style a component without leaking style to other parts of the document.

& templating reduces the amount of writing for authors.

I however think this could remain a preprocessor feature.
That it can not be implemented in browsers doesn't mean that authors are not allowed to have a tool to help them write repeated bits in selectors.

2. grouping styles for different elements but all part of the same component

Example :

.my-component {
  display: flex;
  gap: 3rem;
  margin: 2rem;


  .column {
    width: 50px;

    &.column--left {
      text-align: right;
    }

    &.column--right {
      text-align: left;
    }
  }
}

This source code is still flawed and authors will still need to use things like BEM because the html might look like this :

<div class="my-component">
  ...
    <div class="column">
      ...
        <div class="other-component">
          ...
            <div class="column">

The second .column is completely unrelated to .my-component but it will still match .my-component .column.

@scope solves this and removes the need for BEM methodologies.

3. grouping conditional styles for the same element

Example :

.element {
  color: blue;

  &:hover {
    color: pink;
  }

  &:has(img) {
    color: cyan;
  }

  &.element--red {
    color: red;
  }

  @media (prefers-color-scheme: dark) {
    color: yellow;
  }

  @supports (color: oklch(...)) {
    color: oklch(...);
  }
}

This is the one thing where I think nesting truly shines and it is something that isn't made obsolete by @scope.

4. side effects of the lax syntax in preprocessors

Examples :

.element {
  & + .bar {
    /* writing CSS inside `.element` but the matched element is a sibling */
  }
}
.element {
  :not(&) {
    /* writing CSS inside `.element` but the matched elements are everything except `.element` */
  }
}

It is at least unusual that a nested block can be reached/executed for conditions where the outer block can not.

if (foo) {
  if (!foo) {
    /* unreachable */
  }
}
@media screen {
  @media not screen {
    /* unreachable */
  }
}

I am not convinced these types of selectors are needed.
I am sure that someone will have found a use for them, but are they really needed and do they really help?

5. reduce repetition

Example :

h1, h2, h3, h4, h5 {
  /* styles for headings */

  @media (min-width: 300px) {
    /* styles for headings */
  }

  & + p { /* repeating the selector manually is a lot of work, easier to add "+ p" here */
    /* styles for paragraphs that follow headings */
  }
}

Nesting allows authors to reduce repetition of selectors.
In general I think this is a good thing but I am unsure if it must be tied to nesting.

Can we reduce repetition outside of nesting?
Isn't @custom-selector the better solution to this?

This is much more in line with what exists in other contexts/programming languages.
If you want to reuse something you first assign it to a variable.

@custom-selector :--h h1, h2, h3, h4, h5;

:--h {
  /* styles for headings */

  @media (min-width: 300px) {
    /* styles for headings */
  }
}

:--h + p {
  /* styles for paragraphs that follow headings */
}

Having nesting as the tool to reduce repetition means that people will nest code for this alone. Code they might not nest if @custom-selector was supported.


Proposal

This makes me wonder how nesting could look like if it had never existing in preprocessors and if we had @scope in all browsers today.

Would this be sufficient?

  • only nest at rules which affect the matching or priority of the parent rule (no @property, ...)
  • selectors in nested rules can only be compound selectors containing only pseudo selectors1
  • & doesn't exist
  • @nest doesn't exist

Allowed at rules :

  • @media
  • @supports
  • @layer
  • @container
  • ...

In other words, nesting is only a mechanic to "filter" the elements matched in the parent block.

  • it is simple to learn, both syntactically and which elements will match.
  • it is consistent
  • it is expressive
@scope (.my-component) {
  :scope {
    /* declarations */

    :hover {}

    @media (min-width: 300px) {}
  }
  
  .element {
    /* declarations */

    :focus {}

    :is([data-theme=red]) {}

    :has(img) {}
  }
}

equivalent to :

@scope (.my-component) {
  :scope {
    /* declarations */
  }

  :scope:hover {}

  @media (min-width: 300px) {
    :scope {}
  }
  
  .element {
    /* declarations */
  }

  .element:focus {}

  .element:is([data-theme=red]) {}

  .element:has(img) {}
}

The downside of anything in this direction is that authors will dislike it today.
@scope must have been widely adopted before a version of nesting with these limitation will be perceived as useful.

Footnotes

  1. The requirement for all pseudo's is debatable and the same restrictions as exist in proposal 3 would work. I personally like :is(..) and :has(...).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions