# 5. Cascade The resolver flattens the nested blocks of a file into a flat list of *rules*. The cascade then decides, per element, which declaration of each property wins. This chapter defines both. ## 5.1 The rule record A rule is a record with these fields. | Field | Meaning | |---|---| | `id` | a number, unique inside the document, in creation order | | `sel` | the full selector text, nesting already joined (5.3) | | `media` | a list of normalized media conditions (5.5). All must hold. | | `signals` | a list of signal conditions (5.9). All must hold. | | `containers` | a list of container conditions (5.6). All must hold. | | `layer` | one of `tokens`, `base`, `components`, `utilities`, `pin` | | `order` | a number: this rule's place in the file, counted from 0 (5.4) | | `decls` | a list of declarations: `{ prop, value, line }` plus extras | | `file`, `line` | where the rule came from | | `pseudo` | the pseudo-element name for a shadow rule, else null | | `states` | the set of state words found in `sel` (5.7) | A *shadow rule* has the same fields. It is a rule whose `sel` names a pseudo-element target (5.8). ## 5.2 Layers - **CAS-1.** The layers are ranked `tokens` (0), `base` (1), `components` (2), `utilities` (3), `pin` (4). - **CAS-2.** Rules outside any layer block are in `components`. `@layer { }` puts the rules inside it in layer `` when that is one of the five names, and in `components` for any other name, with the warning `unknown layer "", treated as "components"`. The hint is the did-you-mean over the five names, or `the layers are tokens, base, components, utilities, pin` when none is close. (D-3) - **CAS-3.** `pin { }` puts its rules in layer `pin`. Declarations in `pin` behave as if `!important`: they beat declarations from every other layer, and they beat inline styles that the page itself wrote (5.11). - **CAS-4.** The layer `tokens` is used only for token rules (chapter 4). - **CAS-5.** There is no specificity. For two matching rules, a lower layer rank loses to a higher one. Within a layer, the later `order` wins. ```dssl .a { color: red } pin { .a { color: blue } } @layer base { .a { color: green } } ``` All three match. `base` is below `components`, so `red` beats `green`, and `pin` beats both. The element is blue. ## 5.3 Nesting A selector block may contain declarations, other selector blocks, state blocks, pseudo-element blocks, `@media`, `@container`, `@signal`, `@layer` and `pin` blocks, and `use` lines. - **CAS-6.** Nested selectors are joined to the parent selector. If the child contains `&`, each `&` is replaced by the parent selector. Otherwise the result is the parent, a space and the child (a descendant selector). A block at the top level is used as written. - **CAS-7.** Lists are split at top-level commas (not inside parentheses or brackets, so `:is(.x, .y)` stays whole) on both sides. Every parent part is joined with every child part, parent-major: for each parent part in order, each child part in order. The results are joined with `, `. So `.a, .b { .c { } }` means `.a .c, .b .c`, `.a { .c, .d { } }` means `.a .c, .a .d`, and `&` is replaced by each parent part in turn. With no commas on either side the result is the plain join of CAS-6. (Before 0.16.3 the lists were joined as whole text, giving `.a, .b .c`. That was D-7.) - **CAS-8.** A nested block whose header is a single plain word that is close to a state word but not equal to it (`hove`, `focs`) is still a selector, but the resolver reports a warning: `"" compiled as a descendant selector`, with the hint naming the state. This applies only when the header is a bare word of letters, digits and dashes, and there is a parent selector. "Close" is the did-you-mean rule (9.3). The warning never fires for a word that is an HTML element name (`li`, `td`, `menu`, and the rest of the list in the reference engine), because a nested element name is a descendant selector on purpose. (Before 0.16.3 `li` was warned about, being two edits from `link`. That was D-8.) - **CAS-9.** Conditions nest. `@media`, `@container` and `@signal` add one entry to the `media`, `containers` or `signals` list of everything inside. A layer block replaces the layer of everything inside. - **CAS-10.** A state block adds `:` to the parent selector (5.7). A pseudo-element block makes a shadow rule (5.8). ```dssl .card { background: color.card hover { background: color.paper } @media dark { background: color.ink } &.on { background: color.accent } } ``` ## 5.4 Order - **CAS-11.** Each rule, shadow rule and `raw` block gets the next order number at the moment it is emitted. The registry rule (4.3) gets 0. Token rules get a number at the moment their block is reached. - **CAS-12.** A rule is emitted when its block ends, or when a nested block starts, if it has declarations waiting. So a block with declarations before and after a nested block makes two rules, one for each fragment, in source order: ```dssl .a { color: red hover { color: blue } padding: 4 } ``` This makes `.a { color }` (order 1), `.a:hover { color }` (order 2) and `.a { padding }` (order 3). The hover rule beats the base rule because it comes later. Writing the hover block before `color: red` would make `color: red` win while hovering. - **CAS-13.** A nested block is fully walked, and its own rule is emitted, before the rest of its parent. Blocks nested inside it are emitted first. - **CAS-14.** A rule with no declarations after resolution is not emitted. - **CAS-15.** Declarations that appear with no selector around them are the error `declarations must live inside a selector`, at the line of the first (line 0 for the file top level). - **CAS-16.** `order` is local to a file. When the host loads several files, it MUST place them in load order and give every rule one position in that single sequence: the numbers of a later file continue after the largest number of the earlier one. A later file wins over an earlier one for equal layers. Why: before 0.8.2 each file counted from zero and the numbers were compared across files, which made a later file lose to an earlier one for no visible reason. ## 5.5 Media conditions `@media ` adds one normalized query to `media`. - **CAS-17.** The query is trimmed. An empty query is the error `@media needs a condition` and the query `(min-width: 0)`. - **CAS-18.** The single words `dark`, `light` and `reduced-motion` (any case) become `(prefers-color-scheme: dark)`, `(prefers-color-scheme: light)` and `(prefers-reduced-motion: reduce)`. - **CAS-19.** `N` or `>= N` becomes `(min-width: Npx)`. `<= N` becomes `(max-width: Npx)`. `A..B` becomes `(min-width: Apx) and (max-width: Bpx)`. `N`, `A` and `B` are numbers. - **CAS-20.** Any other query is kept, except that in any comparison, a bare number after `>`, `<` or `=` that has no unit gets `px`: `(width >= 768)` becomes `(width >= 768px)`. - **CAS-21.** A condition holds when the host's media query evaluation says it holds. ```dssl .a { @media 768 { padding: 24 } @media <= 480 { padding: 12 } @media 480..960 { gap: 4 } } ``` ## 5.6 Container conditions - **CAS-22.** `@container ` adds one normalized query (same rules as 5.5, with the empty case `(min-width: 0)`) to `containers`. - **CAS-23.** A container condition holds for an element when the nearest ancestor (or the element itself) that the page marked as a container has a content width that satisfies every bound in the query. A bound is `(min-width: N)`, `(max-width: N)`, `(width >= N)` or `(width <= N)`. If no ancestor is a container, the condition does not hold. ## 5.7 States - **CAS-24.** A state block `name { ... }` in a block with selector `S` makes a rule with selector `S:name`. When `S` is a list, the state is added to every part (`.a, .b` with `hover` is `.a:hover, .b:hover`). The state words are in appendix A. State blocks can nest (`hover { focus { } }` gives `S:hover:focus`). - **CAS-25.** A state block with no selector around it is the error `"" needs a selector around it`. - **CAS-26.** All state words except `seen` are the CSS pseudo-classes of the same name and are matched by the host. - **CAS-27.** `seen` is not a CSS pseudo-class. An element is *seen* once at least part of it has been inside the viewport, and stays seen. A rule whose selector contains `:seen` matches when the selector with `:seen` removed (or `*` if nothing is left) matches and the element is seen. An implementation MUST re-evaluate the element when it becomes seen. - **CAS-28.** Inside a `seen` block, a value that contains `->` is the error `": " - the -> arrow is motion syntax and does nothing inside seen { }`. The rule is still emitted. - **CAS-29.** `states` is the set of state words found in `sel` as `:word`, from the list: hover, focus-visible, focus, active, disabled, checked, empty, target, invalid, valid, required, open, closed, read-only, read-write, placeholder-shown, first-child, last-child, only-child, seen. ## 5.8 Pseudo-elements and shadow rules Inline styles cannot reach pseudo-elements, so the reference engine puts them in a separate stylesheet. A native implementation has no such limit, but the resolver output still keeps them in `shadowRules`, so that results are comparable. - **CAS-30.** A block `::name { }` inside a selector block `S` makes a shadow rule with `sel` = `S` and `pseudo` = `name`. When `S` is a list, there is one shadow rule per part (and per state block inside it), each with its own copy of the declarations. At the top level (no selector), `sel` is `*`. - **CAS-31.** Inside a pseudo-element block, only declarations and state blocks are allowed. Anything else is the error `only declarations and states (hover { }) may appear inside a pseudo-element block`. A state block inside makes a second shadow rule with `sel` = `S:`. `seen` there is the error `"seen" is engine-native and has no meaning on a pseudo-element`. - **CAS-32.** A bare `content:` (no value) inside a pseudo-element becomes `content: ""`. - **CAS-33.** Engine-native declarations (the `@` ones in 6.8) are not allowed on a pseudo-element, except `@animate`. Each is the error `"" is engine-native and has no meaning on :: - a pseudo-element has no DOM handle`, and is dropped. - **CAS-34.** The same applies when the pseudo-element is written in the selector itself (`.card::before { }`). The selector is split at the first top-level `::`. If every part of a selector list has a pseudo-element, the rule becomes one shadow rule per part. If only some do, the error is `selector list mixes pseudo-element and plain selectors: ""` and nothing is emitted. - **CAS-35.** A pseudo-element name MUST be in the pseudo-element list or match the vendor pseudo pattern (appendix A), otherwise: `unknown pseudo-element "::"`. `::slotted()` is the error `::slotted() only exists inside a shadow tree, and DSSL styles the light DOM`. `:seen` combined with a pseudo-element is an error. - **CAS-36.** If every declaration of a shadow rule was rejected, nothing is emitted. ```dssl .btn { ::after { content: "" opacity: 0 hover { opacity: 1 } } } ``` ## 5.9 Signals A signal is a named value that the page sets. It is how DSSL reads state that CSS cannot see (a theme chosen in a menu, a game screen). Chapter 11 says how a browser could expose it. - **CAS-37.** `@signal ` adds one condition to `signals`. The forms are: | Form | Condition record | |---|---| | `name` | `{ name, op: 'truthy', value: null }` | | `name == value` | `{ name, op: '==', value }` | | `name != value` | `{ name, op: '!=', value }` | | `name in (a, b)` | `{ name, op: 'in', value: 'a,b', values: ['a','b'] }` | | `name not in (a, b)` | `{ name, op: 'notin', value: 'a,b', values: ['a','b'] }` | A name matches `[A-Za-z_][\w-]*`. A value may be quoted with `"` or `'`, and the quotes are removed. List values are trimmed, unquoted, and empty ones removed. - **CAS-38.** An expression in none of these forms is the error `bad @signal expression ""` and is treated as `truthy` on the whole text. An empty list is the error `@signal ( ) has no values`. - **CAS-39.** A signal's value is looked up on the element, then on each ancestor, then at the document level. The first place that has the name wins. Values are compared as strings. - **CAS-40.** A signal condition holds when: - `truthy`: the name has a value that is not `false`, `null`, the string `false`, `0` or the string `0`, - `==`: the name has a value and its string equals the condition's value, - `!=`: the name has no value, or its string differs, - `in`: the name has a value and it is in the list, - `notin`: the name has no value, or it is not in the list. ## 5.10 Which rules apply to an element - **CAS-41.** A rule applies to an element when all of these hold: 1. its selector matches the element. A rule with selector `:root` applies only to the document's root element; 2. if the selector contains `:seen`, CAS-27 holds; 3. every `media` condition holds; 4. every `signals` condition holds; 5. every `containers` condition holds. - **CAS-42.** Applicable rules are sorted by layer rank, then by `order` (both ascending). The declarations of the sorted rules are flattened in that order, each rule's declarations in their written order. - **CAS-43.** For each property, the last declaration in that sequence wins. Engine-native declarations (6.8) each have their own combination rule, given with their feature. ## 5.11 What else decides the value These rules are about where DSSL meets the page. - **CAS-44.** *Inline styles written by the page.* A declaration that the page itself put in the element's `style` attribute, or set from a script, beats any rule that is not in `pin`. A `pin` rule beats it. A property the page removes is free for rules again. - **CAS-45.** *Shorthand and longhand.* A shorthand expands into its longhands in cascade position. Of a shorthand and one of its longhands, the one that comes later in the flattened sequence wins for that longhand. This holds for `border` and its `-color`, `-width` and `-style` longhands, and for `padding`, `margin` and `gap` and their sides. - **CAS-46.** *Handing back with yield.* After the sequence is flattened, properties named by the last applicable `yield:` (6.7) are removed from the DSSL result, so ordinary CSS decides them. `yield: none` clears the list. - **CAS-47.** *Alignment makes a box.* If the winning declarations include any of `place-items`, `place-content`, `align-items`, `justify-items`, `align-content` or `justify-content`, and the element has no declared `display`, and the element's natural `display` is not `flex`, `inline-flex`, `grid` or `inline-grid` (and is not `none` or `contents`), then `display` is set to `inline-grid` when the natural `display` is `inline` or starts with `inline-`, and to `grid` otherwise. An explicit `display` from any rule wins over this. Why: alignment properties do nothing on a plain `span` or `button`, and DSSL treats that as a silent failure. - **CAS-48.** *Border and positional shorthands in the inline lane.* A browser that computes styles natively does not need extra rules for CAS-45: the CSS cascade already does it.