[csswg-drafts] [css-nesting-1] Syntax Additions and Alternate Syntax Thoughts (#7877)

Myndex has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-nesting-1]  Syntax Additions and Alternate Syntax Thoughts ==
# Discussion on Nesting Syntax, and Potential Alternates
This is an additional and/or alternate proposal to #7834 and potentially #7854

**SECTIONS:**

- [BACKGROUND & SPECIFICITY](#specificity)
- [NESTING SELECTORS](#nesting_selectors)
- [GROUPING & MULTI-LEVEL NESTS](#grouping_nested_selectors_with_brackets)
    - [Simpler Example](#a_simpler_example)
- [COMBINATORS AND GROUPS](#working_with_combinators_and_groups)
- [QUICK SYNTAX SUMMARY](#quick_syntax_summary)


**INTRODUCTION:**

This post is an expansion of the post I made in #7834. I personally found some of nesting less than intuitive at first look, and that is the genesis of some of the ideas below. Among other things, I never use SASS, so personally do not benefit from incorporating SASS syntax, and not to say that as any form of objection, only that there are other useful possibilities to consider.


## Specificity

At the fundamental level, the purpose of nesting is directly related to specificity, or put another way, nesting is a term for CSS "specificity grouping." I started thinking of it this way from @FremyCompany's example in 7834.

### Problems, Goals, Solutions
Among the issues and solutions I've read about thus far:

**_IDENTIFIED GOALS_**

1) Reducing redundant code.
2) Reducing the need for long multi-combinator selector strings.
3) Improving portability and maintainability of complicated specificity groups.
    - Nesting can bring specificity into a single code block.
    - Keeps related style properties together.
4) Incorporating and grouping media queries and other `@rules` _inside_ the style for a given selector/class, instead of multiple versions of classes in separate `@media` queries.


**_IDENTIFIED PROBLEMS_**

1) Parser efficiency, i.e. clearly indicating the difference between a property/declaration and a selector.
    -This brought the need for the ampersand as the first character in each selector, however there may be other useful solutions instead or in addition too.
2) Potential conflicts of specificity due to nested selectors being at a higher specificity than might be intuitively reasoned for an equivalent serialized selector.
3) Code maintainability and migration considerations from other syntax sheets, and also potential confusion or difficulties tracing back to determine the actual selector chain.
    - Due to 2 & 3, it could be argued that it is best to either use nests all the time, or not use them at all.
4) Potential ambiguities for certain combinators, particularly the space and adjacency to class identifiers ` . # `, and particularly ambiguities for comma separated multiple selector lists.



------
## Nesting Selectors

I find it convenient to think of ` & ` as a symbol that is replaced with the literal parent selector. I believe there is room to add some functionality?

Consider the case of nesting inside a style block that uses a list of multiple parent selectors. As it stands, the `&` is essentially a wildcard for all of the parent selectors. But thinking about regex and capture groups as a feature, it could be beneficial to be able to control which parent selectors applied to a given nest or nest group.

For the purposes of these discussion examples, I'm going to use the regex `$` as the nesting selector symbol for the parent selector(s). In this usage, $ or $0 would mean all of the listed parent selectors, and $1 thru $99 would symbolize each parent selector in the list, in order.


```css
    article, aside, span {
      $ p {font-size: 0.95em;}
      $1 p, $2 p { color: red;}
      $3 p {color: green;}
    }
```

_So this would be functionally the same as:_

```css
    article p, aside p, span p { font-size: 0.95em;}
    article p, aside p { color: red;}
    span p { color: green;}
    }
```

And $n could support a range, as in $3-8

```css
    article, aside, span, div, 
    .myClass, .yourClass, .noClass,
    .masterClass, .stayAfterClass  {
      $3-8.p, $1-4 p { font-size: 1em; }
    }
```

_This would be functionally the same as:_

```css
    span.p, div.p, .myClass.p, .yourClass.p,
    .noClass.p, .masterClass.p, span.p,
    article p, aside p, span p, div p { font-size: 1em; }
    }
```

The following might be difficult to parse (??) but there is a use for ` $! ` meaning that the immediate parent selector(s) would **_not_** be used, and the specificity that would have attached therein is discarded. This would have use for keeping commonality for lower specificity instances of a given selector style.

```css
    article, aside, span {
      $ p, $! p { font-size: 1.2em; }
    }
```
_This would be functionally the same as:_

```css
    article p, aside p, span p, p { font-size: 1.2em; }
```
While this may seem unneeded, consider the use case of setting a default, the advantage in this example keeps the default font-size all in this one place, and the p outside of article, aside, or span at a lower specificity to be overridable by local styles or later selectors.

Another use for ` $! ` is partially breaking out of nested nests and up one level:

```css
  .myBigClass {
     $ article, $ aside {
        $ p, $! p, $!.p { font-size: 1.1em; }
    }
```
_Functionally the same as:_

```css
    .myBigClass article p, .myBigClass aside p, 
    .myBigClass p, .myBigClass.p { font-size: 1.1em; }
    }
```

### _SUMMARY_
I'm only using the `$` instead of `&` because of the familiar usage with regex, and I may use it  in other examples below for consistency in this post. Nevertheless, ` & &! &0 and &1-99 ` might be better here as ampersand is already the nesting selector.



-----

## Grouping Nested Selectors with Brackets
Next let's consider using the nesting selector to define a **nest group** _instead of_ forcing it in front of every nested selector. In this case it might make sense to use a different bracket type for enclosing a group of nested selectors, i.e. square or angle brackets instead of curly braces.

While square brackets are used for attribute selectors, we should be able to use them for nest groups by stipulating that, on the line they are on, they must be proceeded only by whitespace OR a _nesting_ selector as shown above ($, $!, $0, $1-99) but not any other kind of selector.

Grouping with square brackets directly solves the issue of the parser not knowing if it is a declaration or a selector. Square brackets will be used for the examples below.

### _Example_
Here using $[] to define basic nest groups:

```css
section {
  margin: 0;
  padding: 0.5em;
  font-size: 21px;

  @media (max-width: 480px) {
    font-size: 2.2vw;
  }

  $ [
     article, aside, span, div {
        color: #010a12;
        background-color: #e6e0dd;

        $1-2 [  /* specificity group only for article and aside */
           h1 { color: #444; font-weight: bold;}
           h2 { color: #333; }
           .myClass { font-family: Barlow, sans-serif;}
        ]

        $4 $3, $3 $3 { display: inline-block;} /* not group, just a style */

        $ [   /* specificity group for all 4 parent selectors */
           ol, ul {padding: 0.5em;}
           li {line-height: 1.6;}
        ]

     }

     aside {
        background-color: #cde;
        [ p {font-size: 0.95em;} ] /* the opening bracket serves as nesting selector */
     }

  ] /* close the main nest */
}

```

So here, nests are fully enclosed in square brackets (though perhaps angle <> might be more appropriate, TBD). This makes multiple nest levels and the nesting hierarchy more clear. Note also that the opening bracket can serve as the nesting selector if all the immediate parent selectors are to be incorporated to the nested selectors.

The above simple example is functionally equivalent to he following conventional set of styles which is a bit of a complicated mess:

```css
section {
  margin: 0;
  padding: 0.5em;
  font-size: 21px;
}

section article, section aside, section span, section div {
   color: #010a12;
   background-color: #e6e0dd;
}

section article h1, section aside h1 {
   color: #465;
   font-weight: bold;
}

section article h2, section aside h2 {
   color: #333;
}

section article .myClass, section aside .myClass {
   font-family: Barlow, sans-serif;
}

section aside { background-color: #cde;}
section aside p { font-size: 0.95em;}

section article ol, section aside ol, section span ol, section div ol,
section article ul, section aside ul, section span ul, section div ul {
    padding: 0.5em;
}

section article li, section aside li, section span li, section div li {
    line-height: 1.6;
}

section div span, section span span { display: inline-block;}

@media (max-width: 480px) {
  section { font-size: 2.2vw;}
}

```


### A Simpler Example
Here is a simpler version than the above example that still illustrates the main points:


```css
    article, aside, div {
      [ p, span {font-size: 1.1em;} ]
      $1,$2 [ p { color: maroon;} ]
      $3 [ p {color: blue;} ]
    }
```

Functionally the same as:

```css
    article p, aside p, div p,
    article span, aside span, div span {
      font-size: 1.1em;
    }

    article p, aside p { color: maroon;}

    div p { color: blue;}
    }
```


------
## Working With Combinators and Groups
A side effect of grouping and multiple groups is potential ambiguity regarding some combinators. Here are some rule considerations.

- The **default combinator** between a nesting selector and a **`.class` or `#ID` selector** _inside_ a group is the descendent combinator (i.e. a space).
    - If there should be no space, add a period either immediately after the nesting selector (`$. [ .myClass{` to apply to all grouped selectors) or immediately before the class or ID (`[..myClass` or `[.#myID`).
- The default between a nesting selector and a **pseudo element** is no space.
    - If a space is needed before the pseudo element's colons, add the universal selector (`*::pseudo`) immediately before the colons (the universal selector must be _after_ the opening bracket).
- When the combinator is preceding the opening bracket, the combinator applies to all of the grouped selectors and the nesting selector that is before the combinator.
- To use a combinator with only certain selectors in the group, the combinator must be _after_ the opening bracket.


```css
  article, aside, div {
    $2, $3. [ 
      .myClass {font-size: 1.1em;} 
      .myOtherClass {font-size: 1.4em;} 
    ]
  }
```
Results in:

```css
   aside .myClass {font-size: 1.1em;} 
   aside .myOtherClass {font-size: 1.4em;} 
   div.myClass {font-size: 1.1em;} 
   div.myOtherClass {font-size: 1.4em;} 
```



------
## Quick Syntax Summary
The examples above sum up as:
### Nesting and Grouping Selectors
- **The `$` Nest Selector variations**
    - `$` or `$0` — All parents listed in the immediate parent selector list.
    - `$n` — only the nth parent in the list of parent selectors.
    - `$m-n` — the range of parents in the list from the mth to the nth.
    - `$!` — _none_ of the parents listed in the immediate parent selector list.
    - `$ p {font:..;}` — non-group nested items need only the `$` (no square brackets).

- **The `[]` Group Nesting Selector**
    - `$ [` — an opening bracket must be proceeded _**only**_ by whitespace, a nest selector, or a combinator.
    - `[` — an opening bracket may act as `$0` _WHEN_ it is _first_ non-whitespace character on a line.
        - `$2`[...` — `$n` can be used in addition to the opening bracket.
            - `$2,$6-7 [...` a list of multiple nest selectors are comma delimited.
        - `]` — nests using an opening bracket _must_ be closed with a closing bracket.
    - `main { $1[ h1{} h2{} ] }` — multiple independent selectors/styles can be in a group nest.
    - `main { [ [] ] }` — nests can be nested inside each other.
    - `main { [] [] ${} }` — multiple nests can be at the same level.

- **Combinators and Groups**
    - The **default combinator** for `.class` or `#ID` selectors is a space.
        - For no space, add a period (`$. [.myClass{`) or (`[..myClass` or `[.#myID`).
    - The default for a **pseudo element** is no space.
        - If a space is needed add the universal selector (`[ *::pseudo`)
    - Combinators before the opening bracket apply to all grouped selectors.
    - To use a combinator only with certain selectors, it must be _after_ the opening bracket.

-----
The advantage for these suggestions is more granular control over which parent selectors we are nesting under, and a more clear hierarchy by using a different bracket type for nests, with the opening bracket adding the advantage of acting as the default nesting selector. 

Thank you for reading, I look forward to your comments/thoughts.

Andy



Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7877 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 13 October 2022 02:50:04 UTC