Matthew Huntsberry

Field guide

Field guide · 03

Promise the axes, not the outcomes.

A component contract is the list of things a consumer is allowed to vary. Get the list right and the component absorbs new requirements quietly. Get it wrong and every new case arrives as a new prop. The control on this page accepts 4 axes, which produce 108 renderings, and they are implemented by 16 rules.

00

A prop per request is not an API

The usual failure is additive. Someone needs a button with less emphasis, so a subtle prop appears. Someone needs one on a photo, so onDark appears. Six months later the component takes eleven booleans, four of which cannot be true at once, and nobody can say what happens if they are.

Each addition was reasonable. The problem is that none of them asked what the component is actually varying along.

01

Find the axes first

This control has 4. Two are props a consumer sets, two are modes it inherits from wherever it is rendered.

variant

base · primary · neutral · subtle · ghost · inverse

class

state

default · hover · active

state

density

comfortable · dense

mode

radius

sharp · round · default

mode

Notice what is not an axis. There is no colour prop, no size in pixels, no corner radius. Those are outcomes of an axis, and exposing them would let a consumer produce a control the system never agreed to.

02

The matrix is the consequence

Turn the mode axes on below. The count climbs quickly, and the number of rules underneath it does not move.

The contract, rendered

Turn an axis on and the matrix multiplies. Hover any control for the state axis. Click one to see what it resolved through.

18 renderingsfrom16 rules

Hovering is the third axis. You cannot lay all 108 out at once because states have to be triggered, which is a fair preview of why enumerating them by hand goes wrong: the ones you cannot see are the ones that rot.

03

A contract is mostly a list of refusals

6 variants sounds generous until you notice it is a closed set. A consumer cannot pass a hex. They cannot nudge the height by two pixels for one screen. They cannot ask for a corner that no other control has.

Those refusals are what make the 10 component tokens underneath worth having. If any consumer can reach past them, the tokens describe what the system hoped for rather than what it ships.

04

How granular is too granular

The test I use: could two consumers pick different values and both be right? If yes, it is an axis. If the answer is always the same, it is a decision the system should have already made.

Emphasis passes. One screen genuinely needs a primary action and another genuinely needs a quiet one. Corner radius fails, because no consumer has a reason to disagree with the rest of the product about corners. That is why radius is a mode here and not a prop.

Every prop is a question you are asking every future consumer.

Eleven booleans is eleven questions, asked of everyone, forever. Most of them have one correct answer, and the component should simply know it.

05

Two people are trying to standardise this

Everything above is how I work, not a format anyone else can read. Two projects are currently trying to turn the same idea into something portable, and they are solving different halves of it.

DSDS is a JSON format for the documentation. A file declares a spec version and either one entity or groups of them, each tagged with a kind, and entities can point at each other with $ref so a large system keeps one file per component and a manifest on top. The contract lives in optional blocks: api, variants, states, anatomy, design-specifications. Its own framing is the useful part: it documents the how and why, not the values, and defers to the W3C token format for those.

Southleft’s ds-contracts-poc takes the other half. There the contract is the source, not a record of it. One file generates a React library and a Figma library, a three-way differ proves the two match it, and there is a browser playground that runs the loop.

Their Button is worth reading next to the matrix above. It is ds.button, version 1.5.0, and its whole API is a variant enum of primary, secondary, danger and ghost, a size enum of sm, md and lg, two booleans, and children. States are a closed set of hover, focus-visible and disabled. Accessibility is three checkable assertions rather than a paragraph: focus visibility, minimum hit area, contrast.

Both of them enumerate axes. Neither ships a colour prop.

That agreement is the signal. Two teams working independently, one from documentation and one from code generation, landed on closed enums and a closed state list, because those are the only shapes a tool can check.

Neither is ready to adopt wholesale. DSDS is a draft with no standards body behind it and says so on its front page; the Southleft repo is explicitly a proof of concept. What is safe to take today is the shape they agree on. If your components already promise enumerated axes and a closed state list, you can emit either format later. If they promise eleven booleans, no format will save you.

06

What the contract buys you

Adding a seventh variant here means one block of role assignments. It inherits every state, every density, every radius, and it cannot produce a combination the system has not already agreed to. Nobody updates a matrix, because nothing enumerated one.

That is the same trade the token tiers make, one layer up. Enumerate the axes, derive the rest, and the surface area stops being something you maintain by hand.