Re: [csswg-drafts] Add a property to the `CSSPseudoElement` IDL interface to determine if a pseudo element "exists" (#12158)

Following the discussion, I gave this issue more thought. Please, take a look and leave a comment, especially about the table!

## Use Cases (from my head)
1. Conditional event listeners or observers: Attaching listener/observer/animation only if a pseudo-element exists and is being rendered.
2. Performance optimization: Avoid computing layout-heavy styles, if the pseudo doesn’t affect rendering.
3. Testing: Checking for the presence of visual pseudo content is valuable.
4. Conditional content generation: Dynamically {insert additional}/{change existing} content in e.g. `::before`, if it’ll actually be used.

## What should `CSSPseudoElement.exists` mean?
In most layout-related cases (e.g. `::before`, `::after`, `::marker`), "box is generated" sounds most reasonable.

For range-based and semantic pseudos (e.g. `::selection`, `::highlight`, `::cue`, `::grammar-error`, `::checkmark`), a “is active” or “has matching range” approach is better.

So, to be future proof and being able to define `CSSPseudoElement` for more pseudo-elements, I suggest:

`CSSPseudoElement.exists` to return: true / false / null:
- true -> actively present/rendered/... defined on per-pseudo-element basis.
- false -> opposite of true.
- null -> not applicable or indeterminate (e.g., author shouldn’t be relying on it).
This avoids over-promising, while still providing usefulness in most use cases.

## `CSSPseudoElement.exists` Behavior Table

| Pseudo-element         | When `.exists === true`                                                   | Notes                                                                |
|------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------|
| `::before` / `::after` | `content` is non-`none` and generates a box                               | `false` for empty or `content: none`, `null` when no style is defined                                 |
| `::first-line`         | Element has stylable inline text                                          | `false` for empty/non-text elements, `null` when no style is defined                                 |
| `::first-letter`       | Stylable first character present                                           | same as ::first-letter                                |
| `::placeholder`        | Placeholder is visible (no user input yet)                                | `false` when field is filled, `null` when no style is defined                                     |
| `::selection`          | There is an active selection in the element                               | `false` if no text is selected, `null` when no style is defined                                      |
| `::highlight(name)`    | Named Highlight intersects visible element content                        | `false` otherwise, `null` when no style is defined                                              |
| `::spelling-error`     | Spellchecker detects error                                                 | Depends on UA/platform support                                      |
| `::grammar-error`      | Grammar issue detected and styled                                          | Depends on UA/platform support                                                        |
| `::marker`             | Element is styled as `list-item` and marker is rendered                   |   `false` otherwise, `null` when no style is defined                                  |
| `::checkmark`          | Control renders internal checkmark (e.g., via `appearance: auto`)         | Depends on UA and platform conventions                              |
| `::dropdown`           | Native dropdown arrow is shown                                            | `false` otherwise, `null` when no style is defined                                 |
| `::cue`                | Active WebVTT cue is displayed                                             | `false` otherwise, `null` when no style is defined                                                 |
| `::backdrop`           | Element is modal or in fullscreen state                                   | `false` otherwise, `null` when no style is defined                            |
| `::scroll-marker`          | `content` is non-`none` and generates a box and `::scroll-marker-group` exists                               | `false` for empty or `content: none` or no `::scroll-marker-group`, `null` when no style is defined                                  |
| `::scroll-marker-group`    | Defined in style via property and pseudo style and generates a box         |   `false` (or `null` for consistency?) when no style is defined                  |

## Special / Unresolved Cases

| Pseudo                 | Notes                                                                     |
|------------------------|---------------------------------------------------------------------------|
| `::target-text`        | Possibly `true` when fragment-activated text is present                   |
| `::view-transition-*`  | Would need clear phase-specific definitions                              |
| `::part(name)`         | Shouldn't be represented as CSSPseudoElement probably? `true` -> shadow tree contains a matching part                            |
| `::slotted(selector)`  | Shouldn't be represented as CSSPseudoElement probably? `true` -> one or more matching nodes are slotted                 |
| Unsupported pseudos     | CSSPseudoElement won't be obtained for those, but just in case: `.exists === null` if pseudo not applicable in current context           |

-- 
GitHub Notification of comment by danielsakhapov
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/12158#issuecomment-3044139876 using your GitHub account


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

Received on Monday, 7 July 2025 09:20:20 UTC