Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-cascade-6] Named scopes proposal #9742

Open
kizu opened this issue Dec 21, 2023 · 5 comments
Open

[css-cascade-6] Named scopes proposal #9742

kizu opened this issue Dec 21, 2023 · 5 comments

Comments

@kizu
Copy link
Member

kizu commented Dec 21, 2023

This issue is a follow-up to the previous issue: #9741

In that issue, I explored the way :scope works in a nested context:

@scope (.outer) {
  @scope (.inner) {
    :scope.inner .test {
      background: lightgreen;
    }
    
    /* This should never match */
    :scope.outer .test.never {
      background: tomato;
    }
  }
}

My proposal consists of two parts:

  1. Augment the syntax of @scope (https://drafts.csswg.org/css-cascade-6/#scope-syntax), allowing specifying the <scope-name> as a <custom-ident> (or <dashed-ident>?), in a way similar (syntax-wise, not behavior-wise though) how we can specify a name for containers (https://drafts.csswg.org/css-contain-3/#container-rule) with a <container-name>.
  2. Introduce a functional form of a :scope pseudo-class, which could be used with the scope name inside, like :scope(my-scope) (or :scope(--my-scope) if we'd decide on <dashed-ident>).

This way, we could do something like this:

@scope outer (.outer) {
  @scope inner (.inner) {
    :scope(inner) .test {
      background: lightgreen;
    }
    :scope(outer) .test {
      background: lime;
    }
  }
}

Basically, allowing specifying any specific scope defined around our selector.

Some things to think about:

  • Can the same name be used multiple times? I'd say that yes, in which case, similar to anonymous scopes, the innermost one would win.
  • Do we need some way to get the equivalent &, where we'd retain the scope root's selector's specificity? I'm not sure of the use-cases for that, probably could be a separate proposal in itself.

Use cases:

  • Using a named scope for the root one, enabling something similar to the @at-root in Sass, or root reference in Stylus. This could work already with the :scope, but only unless a nested scope is introduced, in which case we will lose the access to the root scope.
  • “Naming” scopes to be reusable inside, as a way to not repeat yourself. This way, scopes could fully replace something like BEM, where we could define a scope for the “block”, and then define sub-scopes for every “element”. Then, we retain an ability to mention “modifiers” on the block and elements separately when specifying various states and cases.

Out of this proposal's scope (sorry): naming scopes as a way to reuse their selectors: this is probably a job for CSS-extensions (label, spec; I did not find a good single issue to mention).

@mayank99
Copy link

I think a scope statement would be useful too? It would be similar to a layer statement.

Create a named scope using a scope statement:

@scope foo (.start) to (.end);

Then use it without specifying <scope-start>/<scope-end>:

@scope foo {
  p { color: red; }
}

@mirisuzanne
Copy link
Contributor

Create a named scope using a scope statement

That would be the naming scopes as a way to reuse their selectors use-case, which is somewhat distinct. Both cases raise the question: how do we handle duplicate scope names? I imagine that problem is solvable in both cases, but I wonder if they would require different solutions?

@scope duplicate (.outer) {
  @scope duplicate (.inner) {
    /* Use the inner version of duplicate? */
    :scope(duplicate) .test {
      background: lightgreen;
    }
  }
}

I'm less sure about a solution for reusable named scopes…

@scope foo (.start) to (.end);

@scope foo {
  p { color: red; }
}

/* does this change the definition of foo for the above paragraph? */
@scope foo (.end) to (.another);

I would expect the later statement to override the former, but that becomes a very surprising behavior if the two definitions of @scope foo come from different files. There's probably a way around that. But in order for these two features to use the same syntax, they would need the same solution. (I imagine that's why @kizu mentioned reuse as 'out of scope'?)

@mayank99
Copy link

@mirisuzanne Ah that totally makes sense, I didn't realize that's what was meant by "naming scopes as a way to reuse their selectors". Should I move it into a separate issue? And maybe this issue should also be renamed to make it more clear.

@mirisuzanne
Copy link
Contributor

Yeah, opening a new issue and clarifying the title here both seem like good ideas. We'll also have to keep in mind that this syntax is the 'obvious' syntax for both features. Which makes it potentially a confusing syntax for either one.

@kizu
Copy link
Member Author

kizu commented May 14, 2024

A few quick assorted thoughts (in a brainstorm mode):

  • I think the best would be to have the named scope be a dashed ident. So @scope --foo … instead of @scope foo (makes it possible to use any keywords alongside the name later).

  • For the duplicate nested names, I incline to make the innermost win, basically what I wrote in the issue:

    Can the same name be used multiple times? I'd say that yes, in which case, similar to anonymous scopes, the innermost one would win.

  • After thinking about it for a while, I think that while the “reusing the selectors/scopes” can be treated as a separate issue, it does not necessarily conflict with this one. And it might be useful to think about both of them together, at least as a way to understand how each could build upon another.

  • I agree that when thinking about reusable scopes, the hardest problem is how to handle multiple definitions. Ideally, I'd want to see them work without overriding each other: from the first foo mention in CSS it will apply to anything that follows it, as soon as it is mentioned the second time, the second mention takes precedence. This would be what authors could expect, and how it can be easily implemented in preprocessors. I guess the issue could be how to serialize something like that? I think this is solvable, but yes, this is the most complex part.

  • If we have reusable named scopes, the @scope --foo (.start) to (.end); — an at-rule without a {} after it could be a good way to define them. If a scope has some content, I don't think it should create a new named scope outside of itself.

    • Although, maybe we could introduce a special keyword for defining such a rule, so it could be used with rules inside, like define, this way we could do either @scope define --foo (.start) to (.end); or @scope define --foo (.start) to (.end) { … } and have the --foo available outside as well (basically, a shorthand for @scope --foo …; @scope --foo { … }).
  • Inside a named scope (or outside if they're reusable), we could allow using some scope inside other scope definitions, introducing a from keyword: @scope from --foo to (.another) or @scope (.another) to --foo or @scope from --foo to --bar.

    • We'd need to introduce some way to mention the “start” and “end” of any scope, and come up with good defaults (I can think of use cases for any combination).
  • When we have a named scope, we could want to have a negation, like @scope not --foo, with styles inside matching anything that is not inside this scope.

  • When we have two named scopes, we could want to be able to have an intersection of them, basically to have a new scope that will match for elements that will match both named scopes: @scope --foo and --bar.

  • I think this feature could be done iteratively: starting with just named scopes, then allowing add features that use it: reusable scopes, mentioning scopes as parts of other definitions, operations on scopes etc.

…Ok, maybe I should stop for now :) Many of these could be potentially opened as separate issues, but it will make sense to do only if the proposal itself will be accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants