Cascade architecture
Specificity fights are a design system's most common failure. This one declares its layer order once, up front, so where a rule sits is a decision rather than an accident of selector weight.
reset
tokensthe source of truth below
base
components
layouts
pages
states
responsive
utilities
How the portal looks different. portal-theme.css is not imported into the tokens layer. The admin and client bundles import it unlayered, because unlayered styles beat every layered style — which is exactly what a surface theme needs to do. That single decision is what lets The Backend read as its own product while still spending the same tokens: three surfaces in this repository — main site, admin portal, client portal — on one foundation, with the portal theme winning on the two that need it. Health Ledger makes a fourth by vendoring the same files and renaming that selector to its own [data-page="ledger"], which is the whole of its divergence.
What the system offers, and what a name may say
A token layer can be complete and still be unusable, because the question a component asks is not “does this token exist” but “which one do I want”. These three answers are the part a snapshot cannot hold: they say what the system offers, so a name that does not belong can be refused rather than absorbed.
Two text tiers, not three
--color-text-primary and --color-text-secondary are the whole ramp. -tertiary and -muted are aliases of the second tier rather than steps below it, because the background is flat at #e0e0e0 and the next grey down fails AA against it. Secondary carries 5.92:1 on the light ground and 12.09:1 on the dark. A third tier needs a lighter surface to sit on, not a lighter grey — dimming text further is how a design system launders a contrast failure into a design decision.
7 gap steps, and the default is one of them
The vocabulary shared by .layout-stack and .layout-row, read out of portal-layout.css at build time:
--flush0px
--tight8px
--snug12px
--compact16px
(default)24px
--loose32px
--wide48px
Writing no modifier gets you the 24px step, which sits in the ramp rather than beside it, so the names read in order: each one is looser than the one above it and tighter than the one below. That is the test a spacing name has to pass, and it is enforced rather than asserted — if a step is added out of order, or the default drifts off the ramp, the naming contract test fails the build.
24px is not the only rhythm on the page. --portal-section-gap (24px), --portal-stack-gap (40px) and --portal-table-stack-gap (64px) space bordered blocks apart — panels, grids, whole tables — and form their own ascending ramp for a different job. A step in one is not a step in the other, and the two were confused for a fortnight because both are measured in pixels.
A colour has one name and a value per mode
Not one name per mode, and not a second name for the same colour used as text. Every semantic colour — the brand red, error, success, warning, info and the seven statuses — resolves to the darker end of its hue under the light theme and the brighter end under the dark one, so the same token can be read on either ground.
It was not always so, and the failure is instructive because it is invisible in the place you design it. These tokens were picked to look right as a filled pill on the portal's dark surface, and as text on a light ground they measured between 3.66:1 and 1.26:1 — the 1.26 being the amber, which is not reading at all. The workaround at the time was a second token: --color-text-warning beside --color-warning, and three --color-status-*-text pairs beside their statuses. Those names existed because the first name could not do its job, which is the shape a system takes when it patches a value with a vocabulary. They are gone; the base token carries both values, and the worst contrast in the set is now 4.64:1.
The exception proves the rule. Text painted on one of these fills cannot read the surface's text colour — in light mode the fill is the dark end of its hue and wants a white label, in dark mode the bright end and wants a near-black one. That is --color-ink-on-fill, and it is the inverse of the surface rather than a shade of it. Two button hover states were using --color-text-primary there and measured 2.01:1 and 1.89:1.
A class is named after its role, never its appearance
This one was answered by two deletions. .bg-white painted black under the portal theme, and .text-muted was not muted — the same fault twice. A name that describes how something looks is a promise the theme layer is free to break, and it breaks it silently, because CSS has no way to report that a class stopped meaning what it says. A name that describes what something is for survives a re-theme: --color-surface-raised can go from white to near-black and still be true.