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.

How To Read This

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.

Color stepMuted to --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.
Border step--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.
FillFor a control with a box and no color headroom, because its contents already sit at --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.
LiftA 4px rise on a card and a mini card, 2px on an attribution, and the card's arrow moves 2px with it. Only for a card, because the lift reads as a bug on anything that is not one, which is why the pager link takes a color step instead.

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.

Components On The Dark Ground

Buttons, and the One That Is Not

Pill.btn--pill
Pill Small.btn--pill-sm
View All.view-all

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.

Filter Chip.chip[data-active="true"]
Filter Chip.chip
Button Pill.btn--pill
Tool Chip.tools__item

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 Step
.btnBorder Step
Outlineresting
Outline:hover
.chipFill
Filter Chipresting
Filter Chip:hover
.chip[data-active="true"]Fill, Selected
Filter Chipresting
Filter Chip:hover
.mini-cardLift
Mini Card
resting
Mini Card
:hover
:focus-visibleOne Ring
Outlineresting
Outline:focus-visible

Nothing 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.

The Same Components On Paper

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.

Button.btn
Filter Chip.chip
Filter Chip.chip[data-active="true"]
Tool Chip.tools__item

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 Step
.btnBorder Step
Outlineresting
Outline:hover
.chipFill
Filter Chipresting
Filter Chip:hover
.chip[data-active="true"]Fill, Selected
Filter Chipresting
Filter Chip:hover
.mini-cardLift
Mini Card
resting
Mini Card
:hover
:focus-visibleOne Ring
Outlineresting
Outline:focus-visible

Body 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.


ColumnColumn
The first cell is bold and primaryThe rest is secondary
Every row rules except the lastWhich does not

A callout, for an aside that should not read as the next paragraph.

Link EmbedThe other allowed raw block, for pointing at something off the page

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
Seven per page, in two stacked columns. The group's gap goes to zero to pay for the height.
.view-all20 → 24
On inline-flex, so it stays on the line it shares with a heading or a button.
.logo16, unchanged
The hit area grows and the box does not. Its width and height are measured in the script and transitioned between a resting and a compact value, so a min-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.

An Install BlockThere is nothing to install. The stylesheet is one file and the runtime is one more, both served from this site, and neither has a dependency to pull. A reader who wants a rule copies the rule.
A Snippet Per SpecimenEvery specimen above is the live class, so a block of markup beside one would be a copy, and a copy can disagree with what it copied. The name under each specimen is the snippet, and the page itself is the markup.
A Component API TableThese are classes, not components with props, so the table would list selectors and their declared values. That is a second index of the stylesheet, and a second index drifts from the first. The first is the stylesheet's own comments, which have the advantage of sitting against the rules they describe.

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.