4. Selectors, Cascade & Specificity
A rule is selector { declarations }. A selector is one or more
space-separated units; the space is the descendant combinator. A unit is a type
|box| (with an optional #id, |table#main|), the link type |-|, its
drawing subtype the dimension type (-) (SPEC 15.6), a class
.hot, or an id #hero:
|box| { … } // every box (element selector)
|-| { … } // every link — a line in the identity capsule ([SPEC 9](#9-links))
(-) { … } // every dimension — the |-| subtype ([SPEC 15.6](#156-dimensions))
.hot { … } // every node with class .hot
#hero { … } // the one node with id hero
|table| |box| { … } // every box inside a table (descendant)
#g |-| { … } // every link written in #g
.sidebar |box| { … } // every box inside a .sidebar
|table| .hot { … } // every .hot inside a table
A descendant selector matches a node (or link) whose ancestor chain contains each
unit in order (not necessarily adjacent), exactly like CSS’s descendant combinator.
Every construct keeps its sigil, so a selector reads as a run of marked units; a
bare word is never a selector. |-| and its dimension subtype (-) are
selector-only: a link is drawn by an operator, never instantiated (SPEC 9).
A type’s class never glues into its bars (|box.hot| is rejected): a class is
worn, not part of identity. To match boxes-with-a-class, style the class
(.hot { … }); to match within one, use a descendant (.hot |box|).
A define’s declarations (SPEC 3) are the new
type’s defaults — tier 1 below; its optional [ ] children materialize per
instance (SPEC 9).
Selecting vs. drawing is decided by the section, not the syntax. |box| .hot
in the stylesheet is a descendant rule (.hot inside a box); on the canvas it is
an instance (a box wearing .hot).
The cascade
Properties on a node merge by a fixed five-tier ladder — the more specific source wins, ties broken by later wins (source order). It is CSS-shaped but not CSS specificity: a descendant rule always loses to a class rule, whatever units it names. The tiers, low to high:
- Type cascade — walked from the base primitive up to the node’s declared type,
layering each type’s element-rule (
|box| { }) and define defaults. A more-derived type overrides what it builds on. (This is where a template’s and a define’s baked defaults live — SPEC 8.) - Descendant rules —
|table| |box| { },.sidebar |box| { }, matched against the ancestor chain. - Class rules —
.hot { }, worn via|box| .hoton the node (a text leaf wears them the same way —"x" .hot, SPEC 3). - Id rule —
#hero { }, the node’s own id. - The instance’s own block —
|box#client| { fill: white }— the most specific, beats everything above.
A link walks the same ladder — its type is |-|, its ancestors are its scope’s
container chain, it has no id: below tier 1 sit the baked link base plus the scope’s
clearance / routing, then the |-| element rule (type), descendant |…| |-| and worn-class
rules, then the link’s own block (SPEC 9). One exception: a link into a
node’s own descendant (x → x.path — containment, or a tree’s branch fan) cascades
as if written in x — its ancestor chain is x’s own, so #x |-| { } reaches it. A dimension is a link
subtype — type chain |-| → (-) — so a (-) { } rule beats |-| { } for
dimensions (the more-specific type, tier 1); (-) matches the measuring ops
only — a leader is styled through |-| (a leader-specific selector is
deferred, SPEC 24) (SPEC 15.6).
Complex values replace wholesale. The merge is per-property, not deep:
translate: x y or padding: t r b l on a higher tier replaces the whole value from a
lower one, never blending component-by-component. A pinned child ignores cell: —
pinning takes it out of the grid (SPEC 5).
Inheritable properties (the text family, color, clearance, routing) additionally
flow down the tree — nearest ancestor wins — independent of the specificity tiers
above (SPEC 6).