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:

  1. 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.)
  2. Descendant rules|table| |box| { }, .sidebar |box| { }, matched against the ancestor chain.
  3. Class rules.hot { }, worn via |box| .hot on the node (a text leaf wears them the same way — "x" .hot, SPEC 3).
  4. Id rule#hero { }, the node’s own id.
  5. 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).