- From: Lea Verou via GitHub <noreply@w3.org>
- Date: Fri, 18 Jul 2025 20:51:57 +0000
- To: public-css-archive@w3.org
LeaVerou has just created a new issue for https://github.com/w3c/csswg-drafts: == [selectors] Declarative custom `:state()` states == The topic of allowing authors custom selectors has come up periodically, but we have no concrete proposal yet. For custom elements, JS can define arbitrary states that can be matched through the [`:state()` pseudo-class](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-custom). I just [proposed](https://github.com/whatwg/html/issues/11466) expanding `:state()` to regular elements as well (through a similar JS API). I wonder if we could feed two birds with one scone and piggyback on `:state()` for custom selectors as well. We could have a `@state` rule that defines matching rules for a custom state: ```css @state heading { matches: h1, h2, h3, h4, h5, h6, [role=heading]; } ``` Now `:state(heading)` can be used to match any heading. ## Detailed discussion ### Multiple `@state` rules cascade Instead of `@state` rules with the same id overwriting each other, they could be composed, providing another way to satisfy some of the #10222 use cases. Yes, it would require a double handshake, but perhaps authors can reach a convention for certain states, especially if there is only one mechanism for both CSS and WCs. ```css /* Root */ @state checkbox { matches: input[type=checkbox]; } @state checked { matches: :checked; } /* Custom component */ @state checkbox { matches: foo-checkbox; } ``` Note that `<foo-checkbox>` doesn't need to override anything about `:state(checked)`. As long as it sets the proper state in its JS, it Just Works™. ### States added via `@state` combine with `ElementInternals.states` (and the proposed `Element.states`) The resulting states are a **union**. This means, if a component removes a state via JS but the same state is in effect through an `@state` rule, it still applies. ## Use cases * Maintainability around complex selectors (both in terms of brevity and having a single source of truth) * Allowing the host page to provide extension points (e.g. by styling `:state(button)` instead of `button, .button` ## Potential future improvements - In L1, **specificity** would always be `0, 1, 0`. In L2, a `specificity` descriptor could be used to override. - In L1, all selectors would be combined, and thus there is **no way to *exclude*** from the matching (except via a convention, e.g. including `:not(.exclude-this)` in the selector). L2 could introduce descriptors for this (e.g. an `except` that also cascades), or even a descriptor that makes the rule behave in a last-one-wins manner. - In L1, **pseudo-elements** are simply not allowed in `matches`, just like they are not allowed in `:is()`. L2 could relax this. - In L1, these rules would be tree-scoped. L2 could introduce a descriptor for defining a custom state that also applies to all shadow trees. ## Pros & Cons - ✅ A single mechanism for both WC, elements, and CSS maximizes the odds of de facto conventions emerging, and reduces the API surface authors need to understand. - ❌ `:state()` may be a fitting name for some selectors, but not so much for others - ❌ No way to have parameterized pseudo-classes (but that is an open problem for custom states as well) Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/12502 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Friday, 18 July 2025 20:51:58 UTC