Components
What the tokens are built into, on both grounds, rendered from the same stylesheet the site uses. Hover anything here. The state belongs to the component, and is not a drawing of one.
Every component below is the live class rather than a copy, so one that is wrong here is wrong on the site. Under each is the class that makes it that specimen, which is why a modifier appears without the base class it modifies. A framed area is a specimen area, and that frame is a line with no fill, because a fill is a ground.
Every specimen is shown on both grounds, because a component that is wrong on one of them is wrong on half the site.
Hover and Focus
Four answers, and what a component already is decides which one it takes. A component with a border steps the border, one with a box steps the fill, one that is a card lifts, and everything else steps its color. None of it is decoration. Every rule below is the only thing telling a reader that something is a control.
--on-ground-hover, nine rules. For text that is already a link, and for the one part of a composite control that rests muted. That covers the view all link, the footer and social links, the email address, the FAQ chevron, the pager's label, the attribution's icon, and a link in body copy.--line to --line-hover, five rules. For a control that already draws an edge, where a fill would be a second thing arriving. The outline button, the tile link, the scroll to top button, the link embed and the tool chip all take it.--on-ground. The ground decides the value, so the bar's controls take --chrome, which now means the crumb, the entry switch, the project preview, the menu toggle and the filter chip. An overlay link takes --chip-hover, and so does a chip that is already selected, because a filled control cannot recede. The pill takes --cta-hover, being the one thing on the site that arrives already filled.Focus is one 2px outline on --on-ground, declared once for every link, button, role of button and summary on the site. No component carries a partner of its own. All four answers and that ring are forced further down the page, on both grounds, so none of them waits for a pointer.
Hover never carries information the resting state does not already show, so a keyboard user loses nothing by not having it. Proving that rule is the wordmark, which has no hover at all. It already sits at --on-ground, so a color step has nowhere to go but down, and a fill behind it would turn the one fixed mark on the page into a button.
The bar's four controls sit behind a hover: hover query and nothing else on the site does. They are tapped constantly on a phone and stay on screen afterwards, so a fill left latched by a tap would be visible until the next one. A tapped card is gone before anyone sees its lift, which is why that one needs no guard.
One control takes a fifth answer and it is the only one that can. The scroll-up floats over whatever a reader is reading, so while it floats it sits at 0.6 and comes up to full when pointed at or tabbed to. Where the other four say that a thing is a control, this one says how much of the page it is willing to cover, which is a question nothing in the flow has to answer.
Buttons, and the One That Is Not
A filled pill is a primary action, and Book a Call is its only user. The project preview was the second until it moved into the bar, where it takes the bar's own convention rather than carrying a call to action through a whole case study. An outline is a secondary action on a page with no primary one, and the 404's way back is its only user. The fourth is not a button at all: a link to more of something is not an action, so it stays a plain link rather than a box, wherever it appears.
Pills, and the One That Left
Two components are pill shaped now and share padding, border and radius. A button is a primary action; a tool chip is content in a case study, set at the size of the eyebrow that introduces it. Type inside each follows its context.
A filter chip was the third until it joined the bar. It is a control in the chrome at the same height as the four in an entry header, so a reader moving between the two headers was meeting two vocabularies for one gesture. That family turned out to be a shape and not a kind of thing: of the three, only the tool chip lives inside a case study, and the other two are both controls.
Resting, the bar sits at --seam, 0.22. Pointing at a chip lifts it one step to --chrome at 0.26, and a selected chip sits one step above that at --chip, 0.31. Three values, one step apart, in the order a reader would guess. Both chip tokens were named for this component, and this is the first thing to use them as named.
Rows That Run off the Edge
Two rows in the chrome carry more than fits and scroll sideways, the filter chips and the meta strip under an entry header. Both say so by fading their own contents out at the edge with .scroll-fade, 40px of it on a pointer and 28 on a phone, where a tenth of the row is more to give up.
A mask rather than a painted gradient, because a gradient has to name the ground behind it and these two sit on different ones. A mask reads only opacity, so one rule serves both and nothing about it changes when a ground does. The meta strip paints a ground of its own, so the mask goes on a row inside it; the chips row paints none, so it takes the mask directly.
Cards and Tiles
In the order above: .card, .mini-card, .tile, then .tile.tile--link and .attribution. Card, mini card and attribution all lift on hover. The tile does not move, and the plain tile has no state at all because nothing links from it. Card titles are 18px, mini card titles 16px, tile titles 18px, attribution names 16px.
Every State, Without a Pointer
Four answers again, each beside the resting state it answers from. A specimen shows its state while a pointer is on it and at no other time, which leaves every state a pointer never reaches unshown. These are forced instead, so each can be read beside the resting state it answers from.
.view-allColor Stepresting:hover.chipFillresting:hover.chip[data-active="true"]Fill, Selectedresting:hoverNothing above restates a declaration.
Live rules are re-emitted at load with their pseudo-class swapped for data-state, so a forced specimen draws the rule it names and cannot drift from it. A rule written behind hover: hover shows up here on a phone as well.
A hover that recedes cannot be shown on a page whose ground it recedes to. The bar rests at --seam and lifts to --chrome under a pointer, in that order, so the pair reads here exactly as it reads in the bar.
What Survives the Move to Paper
Half the site is paper, meaning every case study body and every index band. Each component below reads the same rules it reads on the dark ground, and the roles resolve differently because the wrapper rebinds them. Two rules in the whole stylesheet are paper-specific, and both are hovers. An attribution swaps its ground; a filter chip inverts, because it joined the bar and took the bar's own fills with it, and those are fixed tokens no ground rebinds.
.attribution
Both rules the paragraph above names are drawn here. A rule described but not shown is a claim rather than a specimen.
Three components read a value no ground rebinds, and all three mean to. A pill button takes --cta and --on-cta, because a call to action is the one thing a reader should recognize in the same color wherever it appears. A phone bezel in a step sequence takes --bezel-ink, because a device is the same object on any page. Last, the bar takes --seam and its controls take --chrome under the pointer, and neither ever leaves the chrome. Anything else reaching a fixed token from the light ground is the bug the chip was.
Above are .card, .mini-card and .tile again, on the other ground. Neither the filter chip nor the tool chip appears on paper anywhere on the site today. Both are here so that the day one does, it already reads correctly.
Every State on Paper
Six rows again on the other ground, and this is the half that matters most. Both paper-specific rules in the stylesheet are hovers, and a chip reading --chrome on the dark ground reads --raised on this one. Selected carries a rule of its own on this ground rather than inheriting the chrome's fill, which belongs to a ground the chrome never touches.
.view-allColor Stepresting:hover.chipFillresting:hover.chip[data-active="true"]Fill, Selectedresting:hoverBody Copy
The markdown body of every case study and every post, rendered by the publisher into the outlet below. Two of these are raw HTML a writer types into a body by hand: the callout and the link embed. Showing them here also keeps them, since the publisher drops a class selector no node carries.
Heading Two
Body text at 16px on a 1.7 line height, held to a 640px measure so a paragraph stays readable however wide the window is. Strong steps up to the primary role, and a link carries an underline set 3px below the baseline.
Heading Three
- A list shares the paragraph measure and the secondary role.
- Markers take the same color, so they sit behind the words and not in front of them.
Heading Four
Inline code takes the raised fill and the small radius, and a block takes the medium one.
bun site-src/render.ts --publish
A block quote is a pull quote at 24px.
A second paragraph inside it becomes the attribution.
| Column | Column |
|---|---|
| The first cell is bold and primary | The rest is secondary |
| Every row rules except the last | Which does not |
A callout, for an aside that should not read as the next paragraph.
Images and video are part of this block too and are left out here, because both want a real asset. Everything shown resolves through a role: nothing in the block names paper, and nothing would have to change to put a body back on the dark ground.
Target Size
Every control is at least 24 by 24, the floor in WCAG 2.5.8.
The standard also offers a spacing exception, which passes a small target when a 24px circle drawn on it reaches no other one. Three controls here were resting on it.
What changed is how much room that pass had. A footer link was 18 tall with an 8px gap under it. That is 26 center to center against a requirement of 24, so one step up the type scale would have spent the two pixels with nothing looking any different.
Height comes off the box rather than the text. Where a control sits in a stack the container's gap pays for it, so the spacing a reader sees does not move. Boxes 28 tall and flush sit 28 apart where boxes 18 tall and 8 apart sat 26.
.site-footer__link18 → 28.view-all20 → 24inline-flex, so it stays on the line it shares with a heading or a button..logo16, unchangedmin-height would clamp the compact end of that exchange; an ::after overlay gives it the 24 and leaves every measured value alone.Inline links in a sentence are exempt under the same rule and are left alone. Their height is set by the line they sit in, and padding one would push its own paragraph apart. Two remain exempt, and both are isolated by more than 100px: the inline link specimen above, and the wordmark once the bar compacts.
What is not Here
Three things a reader arriving from a published design system will look for and not find. Each is a decision, and without this section each reads as an oversight, and an oversight is the quietest way for a decision to stop being one.
The shape they share is worth naming. Each would restate something that already exists in one place, and a restatement is a thing that can disagree with its source. The forced states above are the counter-example, generated from the live rules at load, so the page can show a rule twice without being able to misquote it.