Skip to content

TYD Design system

TYD uses eight token families: surface, ink, line, type, space, radius, mark, and machine. Each family standardises a pattern found across the published site.

The values below are generated from global.css at build time, so this specification always matches the implementation. A check runs before the build and fails it if a colour token exists in the stylesheet and is not documented here. All 21 are.

Surface

Surface tokens distinguish the page, raised content, sunk panels, alternating rows, mounted evidence, and the paper artwork is printed on.

Ink

Ink tokens establish text hierarchy. The signal colour is reserved for links, and every surface above is that same hue at a tenth of its saturation.

Line

Two hairline weights, because a divider and a boundary are not the same thing.

Type

Type tokens define nine text roles, each shown here at its own size.

Space

Space tokens define the vertical intervals between major page elements, each drawn here at its own height.

Radius

Two corner radii, each drawn here at its own value. Every mat on the site takes the first; anything sitting on a mat takes the second.

Mark

Mark tokens identify list items and sequence states. Each mark inherits the colour of its surrounding text.

Machine

The dark counterpart, for terminals, code, and receipts — and for this page, which is why it is documented rather than treated as a local exception.

Rules

These rules replace repeated or conflicting implementations. Each one exists because the same decision was made twice, and the defect that produced it is the part of this page that could not have been copied from anywhere else.

  1. Use one pattern for alternating rows

    All alternating rows use band-a and band-b, through one selector list.

    There were five implementations across four files in three treatments. Updating one did not update the others, so changing the ledger left the principles list, the before/now ledger and the ratio table on the old grey. A new banded pattern joins the selector list; it does not get its own colours. The same defect had produced two names for one value — band-a and row were both #e9e9ed, kept in step by hand — and row is gone.

  2. Reserve blue for links

    Only links use signal.

    Status indicators in the feedback table were blue squares, which made them look interactive when they were not. The colour is the only thing that says "link", so spending it anywhere else costs the one place it has to work.

  3. Align text to one edge

    List markers and disclosure controls hang in the indent. Their text starts at 26px.

    Numbered lists indented 38px and bulleted lists 22px, so two lists in the same column started on different edges. The disclosure caret later landed on a third at 393px. 26px is what a single digit needs; the wider indent existed for a leading zero that is no longer used.

  4. Let marks inherit text colour

    Marks are masks painted with currentColor.

    A marker is signal inside a link and ink inside a caption without being told which. A fixed image would freeze the fill, and a pseudo-element cannot render a component. Each mask also carries width and height as well as a viewBox, so its intrinsic ratio does not depend on the engine.

  5. Use the mount for evidence and the bands for lists

    Screenshots and embedded applications sit on mount. Alternating list items sit on band-a and band-b.

    Both were reading band-b — a token tuned for separating rows in a list, doing the job of lifting a screenshot off the page. It sat four units from the page background and disappeared. The embed cards had the same defect a second time, on paper-raised: white cards holding near-white applications, meeting the page at 1.03:1.

  6. Declare one size for a page title

    Every page title is --text-display. Nothing is computed per title.

    The size used to be solved per headline so each one would fill its measure, which made the size depend on where the words broke — three lines came out larger than two. Thirteen pages ran 46px to 105px. A title that ends short of the right edge is normal; thirteen sizes of one component is not.

  7. Give every card the same corner

    Anything card-shaped takes radius-mat. Anything sitting on a card takes radius-inset.

    Three values were doing two jobs — 10px, 6px and 4px — with the split falling wherever a component happened to be written, and five card-shaped containers had no radius at all: the feedback table, the surface ledger, the receipt, the needs blocks and the prose aside. Same shape, same job, square corners while everything else was round.

  8. Build every envelope the same way

    Mount, radius-mat, everything inside inset 10px, art on radius-inset, note on the mat 10px under it.

    Seven components draw this envelope — the three embeds, the reconstruction, the compare block, the capture cards and the figures — and they agreed on none of it. The cards were on paper-raised, white holding near-white applications, meeting the page at 1.03:1. The art ran to the card edge on some, so the mat showed only at the corners. The inset was 10px, 18px and 26px. The note was written four times, at four paddings. And one component was missed by an edit that matched the others by exact text, because its rule carried a comment. A white footer strip was tried and dropped: it needed 24px above its text to hold a boundary between two surfaces, and there is only one surface.

  9. Let the page own a figure’s margin, not the file

    Figures are cropped to their drawn content. The white boundary around them is one CSS value.

    The fifteen essay figures carried between 0 and 47px of dead canvas, differing per figure and per edge, and none had a viewBox — so the space between a drawing and its mat was whatever that export happened to leave. Cropped to the content bounds with 2px of slack for stroke width, the boundary became a single padding value, and the same figure now sits identically on the index and inside the essay.

  10. Stack cards two pixels apart

    Cards stacked in one section sit 2px apart, and the corners facing each other are square. Only the outer top and outer bottom are rounded.

    The group is one object with hairline separations, not separate cards that happen to be near each other. The container owns it rather than the cards, because a card does not know whether it is stacked — the essay pairs were 40px apart with every corner rounded, and the rule list 14px apart with every corner rounded, which is the same idea reached two ways.

  11. Build the neutrals from the accent hue

    Every cool surface is hue 210 — the signal’s own — at a tenth of its saturation.

    Four of them sat at 240, a violet-leaning blue, while the signal and the references band sat at 210. Thirty degrees is enough to notice and not enough to name: the neutrals read as a second family rather than as quiet versions of the one accent. The references band came down from 76% saturation to 45% at the same time. Each surface now prints its hue, saturation and lightness on this page, so the next drift is visible rather than felt.

  12. Reserve the label treatment for labels

    Mono, eyebrow size, 0.09em tracking, uppercase — for eyebrows, rail keys and column heads.

    The reference names wore it, which made a person’s name the only thing on the site that was shouted. A name is content. It takes the prose face at sentence case, and the hierarchy against the role comes from weight and ink like everywhere else.

  13. Spend JavaScript against a budget

    One page of eighteen ships script, 328 bytes of it, and the build fails if that grows past 2KB or spreads to a page not on the allowlist.

    The site used to claim it shipped none. That claim lived in a data file nothing rendered, so nothing ever checked it, and the modal made it false. A modal is where the spend is worth it: a <dialog> opened with showModal() moves focus into the panel and traps it, closes on Escape, makes the page behind inert, and draws a real ::backdrop — all of which a styled div has to reimplement and usually gets wrong. A <details> is a disclosure and announces itself as one; a :target overlay has no focus management at all. An absolute nobody measures is worth less than a budget the build enforces.

  14. Test every colour pair that is used

    Each foreground and background pair is checked at the smallest size it appears at.

    The check runs before the build and fails it. When a pair fails, the shared token changes rather than the component, so the correction applies everywhere. A second check fails the build if a token exists in the stylesheet and is not documented on this page — the page claimed to be the system while carrying eleven of twenty-four.