Skip to contentWolf-Rayet

Decision records

ADR-0027 Emphasis is the level; a button has no emphasis variant

Accepted2026-09-01Phase 4, reopened by ADR-0021

#Context

Every design system with buttons has an emphasis ramp. Carbon's is primary, secondary, tertiary, ghost and danger; the names change elsewhere and the shape does not. It is the single most recognisable API in component libraries, and a reviewer opening this system's button will look for it first.

This system also has an emphasis ramp. §2's intensity ladder is one: Ambient is "at or near the substrate, resting content", Marked is "distinguishable, not attention-seeking", Directed is "clearly the local focus". Read those three against ghost, secondary and primary and they are the same statements in different vocabulary — and the ladder's version is the one the budget engine measures, the salience check reads, the token tier is keyed on, and the content linter ties language weight to.

So the question is not whether a button needs an emphasis ramp. It is whether it gets a second one.

What breaks if this stays undecided is not visible on the day it is decided wrongly. Two ramps over one property agree at first, because whoever writes the second one maps it onto the first. They come apart later, one component at a time: a primary button at level 1 because a designer wanted it quiet, a ghost button at level 3 because the engine complained about a Scope. Each of those is locally reasonable and each one makes the ladder a decoration on that screen. The failure is not a bug anyone reports; it is the system's claim about itself quietly stopping being true, which is the failure §3 opens by naming — tiers borrowed from a domain where the boundaries are real, applied where they are not.

The input field is the precedent for the level question and answers half of this one already: ADR-0021 §C caps every form control at Directed because an ordinary interaction produces several at once. That settles the ceiling and says nothing about the variant axis, which is what remains.

#Decision

Emphasis is the level. A button declares level, and there is no emphasis prop, no primary boolean and no kind union. A primary button is level={3}; a secondary button is level={2}; a ghost button is level={1}. The variant axis carries one thing only: whether the action can be taken back — neutral or destructive.

The ladder is carried by ground and weight, not by hue. Levels 1 and 2 stand on surface-inset; level 3 steps up to surface-raised, which is the mechanism the input field's focused control and the table row's selected row already use and the one that still works when there is no hue to spend. The perimeter's weight moves with the level on the same non-decreasing ladder every component in the set uses.

The variant is carried by shape. destructive draws the leading octagon from the shared vocabulary — the status indicator's mark for critical, restated in this component's stylesheet and drift-checked against the indicator's. neutral draws no marker at all, and that absence is the encoding rather than a gap: an ordinary action needs no warning, so a marked button is the one that warns.

Why destructive survives as a variant when primary does not. Emphasis is a claim about how much attention this control should get, and the ladder already makes that claim. Reversibility is a claim about the action's consequence, and the ladder makes no claim about consequences at all — level 3 says "clearly the local focus", not "this cannot be undone". Two different properties, one of which the ladder already carries.

Where the demand belongs. A button cannot reach Demanded, and the reason is not only the plural one ADR-0021 gives. §2's Demanded is "imperative, consequence stated, short" — and the consequence belongs to the *message about* the action, not to the control that carries it out. A destructive action that genuinely warrants the region maximum warrants an alert saying what will happen, with the button inside it at Directed.

#Rejected options

Carry Carbon's emphasis ramp as a variant. Immediately legible to every developer who has used another system, matches the roster this system is being measured against, and could be mapped onto the ladder at the token tier so the two agree by construction. It had the strongest practical case of the four. It lost because "agree by construction" is a claim about the day it ships and nothing enforces it afterwards. The token tier would key marks on both variant and level, so primary at level 1 is a cell that resolves — it is *representable*, and every representable disagreement is a disagreement someone eventually ships. The only structural fix is to not have the second axis, which is this decision.

Keep emphasis as a variant and derive the level from it. A button would take emphasis and compute level, so there is one source of truth and the familiar API survives. Genuinely tempting, and it fails on direction. The ladder is the system's own vocabulary and emphasis is another system's; deriving the native term from the borrowed one makes the borrowed one primary in every call site, every story and every doc page, and the ladder becomes an implementation detail of a Carbon-shaped API. It also does not compose: an alert, a tag and a meter all take level directly, so one component taking emphasis would be the only place in the set where a reader cannot see the allocation in the markup — and the allocation in the markup is what data-wr-level serialises and the engine reads.

**Carry emphasis *and* forbid the disagreeing combinations with a lint rule.** Both APIs, with wr/emphasis-level-agreement reporting a primary button below Directed. It preserves familiarity and catches the failure. It lost on ADR-0021's own consequence clause: "the enforcement surface does not grow with the roster", and the pairing matrix is keyed on layers, levels, themes, densities, motion classes and easings — never on component names. A rule about one component's props is the first check in the system that exists for a single component, and it would be enforcing agreement between two things that only need to agree because both were kept.

Drop the variant axis entirely and put destructive on the level too. The tidiest option: one axis, no variants, a destructive action is simply loud. It lost because it is false. A destructive action is not necessarily the loudest thing on screen — "Purge the queue" sitting quietly at the bottom of a settings panel is correct, and it is still irreversible. Collapsing consequence into intensity would mean either shouting about every destructive action or having no way to say one is destructive, and field-night decides it: with hue suppressed, a quiet destructive button and a quiet neutral one would be the same button.

#Consequences

The button's API is smaller than the industry's and reads differently. <Button level={3} label="Approve the batch" /> is a primary button, and a reader who has not read this record will look for primary first. The Storybook EmphasisIsTheLevel story exists to answer that question where it will actually be asked, and packages/react/test/button-violations.tsx asserts that emphasis, primary and kind are each a compile error rather than a silently ignored prop.

A measurement, not an assertion. 26-button-calm.html holds three identical rows differing only in the button's level. If emphasis is the level, those three Scope shares rise monotonically and the differences between them *are* the emphasis ramp priced in emission — a number a system with two ramps could not produce, because two ramps are free to disagree. The scene also holds a pair differing only in variant, which prices the octagon on its own, apart from the emphasis it is usually confused with.

This generalises to the roster's remaining controls. toggle, select, dropdown, number-input, search, slider, date-picker and link all arrive with an emphasis-shaped axis in the system this roster came from. Each of them is now answered before it is built: emphasis is the level, and whatever remains for that component's variant axis has to be a property the ladder does not already carry.

One thing this makes harder, stated plainly. Migrating a product from another system now requires reading each button's emphasis and choosing a rung, rather than renaming a prop. That is a real cost and it is the point: the rung is a claim about attention that the budget engine will check, and a rename would have carried the claim across unexamined.