Download native/spec/05-cascade.md from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 15.5 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/native/spec/05-cascade.md
- Command line
-
hf download hf://EHS-Studio/dssl/native/spec/05-cascade.md
-
curl -L -o 05-cascade.md https://huggingface.co/EHS-Studio/dssl/resolve/main/native/spec/05-cascade.md
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 <name> { }puts the rules inside it in layer<name>when that is one of the five names, and incomponentsfor any other name, with the warningunknown layer "<name>", treated as "components". The hint is the did-you-mean over the five names, orthe layers are tokens, base, components, utilities, pinwhen none is close. (D-3) - CAS-3.
pin { }puts its rules in layerpin. Declarations inpinbehave 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
tokensis 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
orderwins.
.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:"<word>" 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.3liwas warned about, being two edits fromlink. That was D-8.) - CAS-9. Conditions nest.
@media,@containerand@signaladd one entry to themedia,containersorsignalslist of everything inside. A layer block replaces the layer of everything inside. - CAS-10. A state block adds
:<state>to the parent selector (5.7). A pseudo-element block makes a shadow rule (5.8).
.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
rawblock 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:
.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.
orderis 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 <query> adds one normalized query to media.
- CAS-17. The query is trimmed. An empty query is the error
@media needs a conditionand the query(min-width: 0). - CAS-18. The single words
dark,lightandreduced-motion(any case) become(prefers-color-scheme: dark),(prefers-color-scheme: light)and(prefers-reduced-motion: reduce). - CAS-19.
Nor>= Nbecomes(min-width: Npx).<= Nbecomes(max-width: Npx).A..Bbecomes(min-width: Apx) and (max-width: Bpx).N,AandBare numbers. - CAS-20. Any other query is kept, except that in any comparison, a bare number after
>,<or=that has no unit getspx:(width >= 768)becomes(width >= 768px). - CAS-21. A condition holds when the host's media query evaluation says it holds.
.a {
@media 768 { padding: 24 }
@media <= 480 { padding: 12 }
@media 480..960 { gap: 4 }
}
5.6 Container conditions
- CAS-22.
@container <query>adds one normalized query (same rules as 5.5, with the empty case(min-width: 0)) tocontainers. - 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 selectorSmakes a rule with selectorS:name. WhenSis a list, the state is added to every part (.a, .bwithhoveris.a:hover, .b:hover). The state words are in appendix A. State blocks can nest (hover { focus { } }givesS:hover:focus). - CAS-25. A state block with no selector around it is the error
"<name>" needs a selector around it. - CAS-26. All state words except
seenare the CSS pseudo-classes of the same name and are matched by the host. - CAS-27.
seenis 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:seenmatches when the selector with:seenremoved (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
seenblock, a value that contains->is the error"<prop>: <value>" - the -> arrow is motion syntax and does nothing inside seen { }. The rule is still emitted. - CAS-29.
statesis the set of state words found inselas: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 blockSmakes a shadow rule withsel=Sandpseudo=name. WhenSis 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),selis*. - 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 withsel=S:<state>.seenthere 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 becomescontent: "". - CAS-33. Engine-native declarations (the
@ones in 6.8) are not allowed on a pseudo-element, except@animate. Each is the error"<prop>" is engine-native and has no meaning on ::<name> - 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 isselector list mixes pseudo-element and plain selectors: "<sel>"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 "::<name>".::slotted()is the error::slotted() only exists inside a shadow tree, and DSSL styles the light DOM.:seencombined with a pseudo-element is an error. - CAS-36. If every declaration of a shadow rule was rejected, nothing is emitted.
.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 <expr>adds one condition tosignals. 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 "<expr>"and is treated astruthyon the whole text. An empty list is the error@signal <name> <in|not in> ( ) 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 notfalse,null, the stringfalse,0or the string0,==: 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:
- its selector matches the element. A rule with selector
:rootapplies only to the document's root element; - if the selector contains
:seen, CAS-27 holds; - every
mediacondition holds; - every
signalscondition holds; - every
containerscondition holds.
- its selector matches the element. A rule with selector
- 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
styleattribute, or set from a script, beats any rule that is not inpin. Apinrule 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
borderand its-color,-widthand-stylelonghands, and forpadding,marginandgapand 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: noneclears the list. - CAS-47. Alignment makes a box. If the winning declarations include any of
place-items,place-content,align-items,justify-items,align-contentorjustify-content, and the element has no declareddisplay, and the element's naturaldisplayis notflex,inline-flex,gridorinline-grid(and is notnoneorcontents), thendisplayis set toinline-gridwhen the naturaldisplayisinlineor starts withinline-, and togridotherwise. An explicitdisplayfrom 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.