- From: Lea Verou via GitHub <noreply@w3.org>
- Date: Wed, 29 Apr 2026 16:51:03 +0000
- To: public-css-archive@w3.org
LeaVerou has just created a new issue for https://github.com/w3c/csswg-drafts:
== [css-pseudo] `::before(<ident>)` / `::after(<ident>)`: A possible path forwards for multiple gencontent pseudos? ==
## The problem
`::before` and `::after` are used in a variety of use cases:
- Decorations (e.g. ribbons, speech bubble pointers, turned pages, etc)
- UI icons
- Counters
- Connectors
- Quotes (both just textual ones, e.g. for `<q>`, as well as huge decorative ones for `<blockquote>`)
- Overlays
- [Scrims](https://medium.com/@designwithkabi/ux-drill-03-what-is-a-scrim-0b37dc1e025e)
- [Fancy shadows](https://nicolasgallagher.com/css-drop-shadows-without-images/demo/)
- Separators
- List markers without the restrictions of `::marker`
- Active tab indicator that animates when the tab changes
- Partial borders
- And many other things.
It is very common to need more than two on the same element, as @chriscoyier explains in https://css-tricks.com/use-cases-for-multiple-pseudo-elements/ . We keep introducing specific pseudo-elements to address specific use cases (e.g. `::marker`, `::backdrop`, view transitions), yet the pile of use cases is only increasing.
But the main problem is that this is a **composability failure**. They are effectively a shared resource that different sources of styling (components, design systems, themes, etc) need to compete for or coordinate around. Often, it's easier to give up on them altogether (as UAs do, with very few exceptions).
Worse, because these are used for such vastly different effects, the cascade can produce _terrible_ results when two different sources are styling the same pseudo-element. E.g imagine the combination of code using `::before` to add a UI icon and code using `::before` to add a scrim or a shadow. ðŸ«
Being able to create multiple `<ident>`-keyed `::before`/`::after` would not only solve both problems, it would also open up the way for implementing many things that currently need custom elements and/or shadow DOM via plain CSS, dramatically reducing complexity.
## Prior art
Multiple `::before`/`::after` have been proposed many, MANY times before, dating back to the mailing list era.
- We even have a [wiki entry](https://wiki.csswg.org/ideas/pseudo-elements) dating from …nobody knows when, discussing `::before[n]` and stackable `::before` (e.g. `::before::before`).
- An old (c. 2003) version of GCPM had a [4.2.2. Inserting multiple '::before' and '::after' pseudo-elements](https://www.w3.org/TR/2003/WD-css3-content-20030514/#inserting0) section discussing `::before(n)` syntax.
A few proposals I was able to dig up are:
- [De Bastiani, 2013-03 — ::before(x) proposal](https://lists.w3.org/Archives/Public/www-style/2013Apr/0074.html) — `::before(n)`
- [Adobe's `::nth-pseudo()` proposal by @astearns and @therealglazou](https://opensource.adobe.com/css-pseudo-elements/docs/css4-pseudoelements.html) which goes beyond just multiple `::before` and `::after` but also accepted `before` and `after` but also `letter`, `line`, and `column` too.
- [Minutes of relevant discussion](https://lists.w3.org/Archives/Public/www-style/2012Aug/0771.html)
- https://github.com/w3c/csswg-drafts/issues/6169 Proposes `::trailing(n)` but also floats the idea of `<ident>`! In that, order is determined by a `pseudo-order` property that requires cascading though.
- https://github.com/w3c/csswg-drafts/issues/11670 (declares multiple named pseudos via an HTML attribute)
- [WICG Discourse #964 — Multiple ::before and ::after pseudo elements? (2015)](https://discourse.wicg.io/t/multiple-before-and-after-pseudo-elements/964/)
Nearly all proposals revolved around numerical indices. The only one I could find that touched on idents in passing was #6169.
Thus, the common arguments against them have generally been quite specific to that approach:
1. **Cascade fragility & collisions (the z-index problem):** Indices don't eliminate the shared resource problem, they multiply it. There is no way for a certain styling source to claim a certain index for itself, since integers are by definition global.
2. **Implementation cost:** Each element gets a fixed style-data slot per pseudo today. Allowing `::before(n)` for arbitrary `n` makes that unbounded.
The hope is that this proposal completely eliminates 1 and hopefully mitigates 2.
## Strawman
Taking inspiration from `@layer`, multiple `::before`/`::after` pseudos are declared as parametrized pseudos with `<ident>`, and are all siblings.
```css
.pros > ::before(marker) {
content: "✅";
/* ... */
}
button::before(icon) {
content: "";
display: inline-block;
height: 1cap;
aspect-ratio: 1;
background: currentColor;
mask: var(--icon) no-repeat;
}
.curved shadows {
position: relative;
&::before(curved-shadows-1) {
/* ... */
}
&::before(curved-shadows-2) {
/* ... */
}
}
```
The same pseudo-element using the same `<ident>` on the same target references the same pseudo-element and cascades normally.
### Ordering
Most use cases are separate enough that order doesn't matter much, so we have some flexibility in the algorithm in order to make it easier to implement. Even for the use cases where order matters, it's not a precise order that's desired, the intent is usually first, last, or around the relative order of related pseudos.
A non-negotiable seems to be **immutability**: Once determined for a given element, even if the pseudo-elements get removed (by setting their `content` to `none`), the order cannot be changed.
Determining the order via a property like `pseudo-order` (#6169) doesn't seem viable for a number of reasons (being cascade-dependent etc). Even a second argument on the `::before()` seems fraught when combined with immutability.
Instead, it seems we want some reasonable deterministic default + a way to override.
What could that reasonable default be? Some examples:
1. Parse order (not accounting for conditionals etc). Would need to follow mixins.
2. Order the pseudo-elements are actually created (which would account for conditionals etc).
Open questions TBB:
- Is this internal state or can it be changed by moving rules around via the CSSOM?
- Is the order global, per tree, or per element? Ideally, it should be possible to have different orders for different elements, but it's probably not a dealbreaker if we can't.
Authors can customize the order via a similar mechanism as the empty `@layer` rule, e.g. something like `@before-order foo, bar, baz;` or `@pseudo-order ::before foo, bar, baz;` (and if there is no `::before` or `::after` it's specifying the order for both). `default` could be reserved, so that it can represent the plain version.
Like layers, any pseudos not in the sequence would be appended at the end. For example, this:
```css
@pseudo-order foo, default, bar;
div::before(foo) {}
div::before(baz) {}
div::before(bar) {}
div::before {}
```
Would produce an order of `foo`, (default), `bar`, baz`.
@tabatkins suggested also borrowing dotted idents from layers, though I can't think of any use cases that aren't just solved by namespacing.
## Out of scope
- Enabling multiples of other pseudo-elements (e.g. `::marker`) — this is strictly about `::before` and `::after`
- Nested pseudo-elements (e.g. `::before(foo)::before(bar)` — useful but not MVP
Would love to hear from implementors on whether this general shape might be feasible, and if not, what would make it feasible. cc @emilio @andruud @nt1m
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/13860 using your GitHub account
--
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Wednesday, 29 April 2026 16:51:03 UTC