This is not a component library. It is a product with its system taken out of it. Every rule here was paid for by something that shipped wrong, and each one is written down beside the measurement that produced it. There is no build step and no gate: what you write is what ships, and nothing will stop you doing the wrong thing. That is deliberate, and this page is the trade you get instead.
Five minutes gets you the shape. The contract is
docs/architecture.md and the account of what each rule cost
is in the two CLAUDE.md files.
Three trees and one system. The trees are the same product drawn three times, and each one owns a different half of the truth, which is why a change often belongs in a folder you were not editing.
| folder | what it owns | what it links |
|---|---|---|
| wireframes/ | structure and copy, in grey. A block is decided here first and the colour copy follows | no stylesheet at all |
| ui-visual/ | the visual layer only. 109 screens plus an index, one per state | exactly components/index.css |
| ui-kit/ | the vitrine. A shelf per level, a page per component, six foundations | the system, plus its own stand furniture |
| components/ | the system. 56 stylesheets, one per component plus the substrate | is the thing being linked |
A screen carries no styles of its own, and that is the load-bearing
one. Measured by reading every stylesheet link in the painted tree: 109 of 110 documents
link ../components/index.css and nothing else, and the one exception is the
index of the tree rather than a screen in it. No @media, no
@keyframes, no style= except a datum, the event photograph, or a
value a script writes at run time. If you find yourself opening a screen file to change how
something looks, the change belongs somewhere else.
A state is a page. Loading, empty, error, logged out, chosen: each is its own document in both trees. That is why there are 109 screens and not fifteen, and it is the only reason a state can be measured at all. A rule that cannot be rendered cannot be checked, and it will be wrong the day it first draws.
Almost every rule further down is a consequence of one of these. If a rule looks arbitrary, it is usually the fifth or sixth step of one of them.
1. Two token levels, and the second theme is the proof
A raw value is a primitive. A colour is a role. A component reads a role and never a colour primitive, and there is no third level. The proof that the second level is real is that a whole second theme can be written by re-declaring roles alone. A component that reaches past a role into a primitive cannot be re-themed, and it fails silently: it simply keeps its dark colour on a pale ground. Every specimen on this stand is drawn in both themes side by side for exactly that reason.
2. Green and red are outcome semantics. Brass is the brand
Green means YES and red means NO, on a market outcome, and nowhere else. A success message is not green here and a validation error is not red. The brass is the only thing in this product that is the brand, and it carries every primary action, which is why the button beside the pair is not green. Borrow an outcome colour for an accent and you have taught a reader that a saved form is a winning bet.
3. A level is containment, not importance
An atom holds nothing from the system. A molecule holds atoms or its own named parts. An organism holds molecules or is a screen shell, and that is the ceiling. A pattern is a composition standing on three screens or more, carries arrangement only, and holds no colour, face, border or surface at all.
The level is not a label on a page, it is the order of
@import in components/index.css: a part is imported before the
whole that holds it, so a later rule can out-specify an earlier one without either raising
specificity. Moving a file between groups is a level decision, not a tidy-up. Two screens
is a candidate, not a pattern, and it stays markup.
4. Three rungs, in rem, named by what arrives at them
They are in rem, so a reader who set a 24px default reaches the
desk at 960 and keeps one column until then. The registry in tokens.css is the
instrument, because @media cannot read a var(): the literal in
every query has to be a number on the list, and a number that is not on the list is a rung
somebody invented quietly. Counted today: 35 width queries in the system and 0 in any painted
screen. Read the whole argument on responsive.
5. Movement names its job before it is written, and there are three
--dur-fast, .16sa control answering a finger--dur-slow, .25san element saying it is here--pulse-period, 1.4sa process still running. A period, not a rungA moment for which none of the three can be named does not enter the register
and never gets a movement. Under prefers-reduced-motion the tokens themselves are
re-declared, so a component that reads them obeys without knowing the setting exists, and
there is no blanket * net and will not be one: under it a component that
reads no token is indistinguishable from one that reads every token, so the check can no longer
fail. Operate the specimens on motion, because a screenshot shows a
frame and never a movement.
In this order. Each step ends in something you can look at rather than something you have to take on trust.
components/card.css and read only the header. It names
every class the file styles, every token it reads, its page on this stand, and how many
screens carry it. That header is a contract: if a class is listed there and the file
does not style it, that is a defect, and the sweep for it runs to zero today.components/CLAUDE.md before debugging. It is a list of things that were true and
looked fine, and there is a real chance yours is already in it.Four of these cost this repository days rather than hours. They share one shape: a rule that was present, correct, and not applying, which is the failure no test reports because nothing throws.
Write transition on a face and it replaces the list the atom
declared, silently dropping every property the atom named. Sign-in buttons lost
color and box-shadow that way. The response belongs to the
control: one declaration on the atom, carrying the union of what any face changes.
A box whose opacity is under 1 is a stacking context, so a panel fading in
has its own z-index resolved inside that group for as long as the fade
runs. Menus opened under the first card for 160ms and then jumped on top. The lift
belongs to the box the fade turns into a context, not to the panel inside it.
An SVG with no fill is black. A link with no colour is the browser's blue. A
class the system does not define takes the user agent's own button, which is grey 239 in
Chromium and 192 in WebKit. Reading the source cannot see any of it. Read the
computed page, in a browser, in both engines.
A component page said "242 placements" and drew one glyph; the 242 are three different marks, one of which does not exist on a phone. A verdict about a component is a statement about the SET of its placements, and where placements disagree you say so rather than picking one.
Nine declarations were keyed to a document-unique id, so two of the components could not be placed twice in one document by anybody. Nothing was broken until a stand tried to show one component twice. No component in a design system may make that promise.
The markup renders either way. The only licence a class has to draw nothing is a
Script hooks: line in the owning stylesheet's header, and four files carry one.
Anything else that answers to no selector is a defect wearing a component's name.
The vitrine that stood here had 41 gates, 54 generators and 145 MB of screenshots, and it was deleted in one commit without the product moving a pixel. The measurement had become a machine that was re-paid on every edit. So every check is an ACT: write the script in a scratch folder, run it, write down what it said, and delete it. What stays is the report and the reason.
pointer:fine, so a tap-target sweep without touch emulation measures a product
with the 44px floor switched off, and every number it prints is about a page that does not
exist.file:// every document has its own
opaque origin, so anything fetched in CORS mode never arrives.All six are in the root CLAUDE.md
with the measurement that produced each, and section 14 of
the contract is the short form.
A fact written twice is a fact that will drift, so each of these is written in one place and referred to from everywhere else. If you find the same number in two files, one of them is already wrong.
| question | owner |
|---|---|
| how the system is entered, and what you may not do | ui-kit/docs/architecture.md |
| what each rule cost, dated, with the measurement | CLAUDE.md and components/CLAUDE.md |
| the visual language and why each choice was made | DESIGN.md |
| what the product is, and the market catalog | PRODUCT.md |
| every component's level, placements and behaviour on width | docs/inventory.md |
| the ladder, and the audit for every screen | docs/responsive.md |
| the motion transcript, the moments and the rulings | docs/motion.md |
| the system read against the product | docs/consistency.md |
| every string the product says | voice/docs/microcopy.md |
| where a reader can go, and the SEO layer | ia/docs/ |
| what was done and why, newest first | docs/decisions.md |
| what is still open | docs/backlog.md |
| which stage is done | the status table in README.md |
| where a file lives | STRUCTURE.md |
One last thing, and it is the habit rather than a rule. Every number on this page was measured on the day it was written and says so. A count typed into prose is a count that goes stale, and this repository has caught itself doing it often enough to stop trusting any figure that does not name the day it was taken. When a number here disagrees with the repository, the repository is right.