The button, six ways

The dial was drawn five ways before one was chosen. This is the same exercise for the thing next to it. There are already three buttons in the house that were never named as buttons — the mark, the pill, and a boxed one drawn five ways across four apps — and CHROME.md counts the cost: 63 button rules in font-proofer, 50 in ReCal, 16 in Kernpare, most of them that same box with its own padding and its own radius.

Two things get decided here, and only two. One enclosure — what a thing looks like when pressing it does something. One editable mark — what text looks like when it is yours to change. They are one decision because they are the same question asked twice, and because getting the second one wrong is how you end up with a bulleted list of unformatted links that each open a modal, with nothing on the page to say so. Every family below answers both in its own language, on the same content, so the comparison is between designs and not between demos.

The confirm

A press is answered twice. :active acknowledges it while the pointer is down — unchanged. When the press worked, the control confirms it the way macOS blinks a menu item: the held look goes off for 80ms, comes back on for 80ms, then the control settles into its result. A blink, not a fade; paint only, nothing moves. Every button on this page does it, each in its own held look. Press these:

Not on a drag, not on a hold-repeat (a stepper held past 400ms confirms nothing), not on a press that failed its own check. Reduced motion: no blink, straight to the result. In the system it is wmConfirm(el) from src/confirm.js and one keyframe set in src/motion.css; GESTURES.md §14.

What all six obey

radiusA constant, never a function of the box. A display button takes the same corner as the one it is louder than; only the type, the stroke and the space change with size. The pill is the exception that proves it — 999px means half the height, so it is allowed only on a single-line control whose height is fixed, and anything that can wrap takes the constant instead.
cornersuperellipse(1.2), radius on the 2^k scale. Safari has no corner-shape and degrades silently to the plain radius — which is the corner every app shipped before the law, so nothing regresses.
target44px on a coarse pointer, bought as padding, never as height. A target has to exist; it does not have to be visible, and it must not be paid for by shrinking the mark.
padding-x≥ 0.6 × the radius. On a pill the radius is half the height, so a 34px pill owes 10.2px each side.
inkfaint / quiet / full = cannot act / present / chosen. Hover and pressed must not share an ink, or the ladder collapses.
markMaterial Symbols, one weight (300). Emphasis rides GRAD, so a row of marks cannot shift by a fraction as one lights up.
pressThe stroke lands before the fill: the stroke acknowledges the tap, the fill confirms on release.
confirmA press that worked blinks its held look: off 80ms, on 80ms, then the result. Never on a drag or a repeat.
focus2px, offset 2, on the signal. Never removed, in any family.
labelA button is text. A mark is an addition that has to earn itself, and most labels never do: “Kern lists”, “10 Reg”, “Average pairs” and “Next specimen” name things no icon is about, and giving them one anyway is how a set ends up with marks that decorate instead of identify. Every button below is therefore text only. Two exceptions, both deliberate: a menu row, where the items are of equal weight and you are picking between them rather than reading them — so the mark is what you scan and the name is the confirmation; and the icon-only button, which is not a label with a mark but a target that is only a mark.
try
01

Keyline

A box, and one side of it. A hairline on a surface, and an editable line is the same box drawn with only its bottom edge — same radius, same inset, same color — which completes as you approach. The law is one sentence: a closed keyline means press; a bottom-only keyline means change. It holds only if no rule under a heading is ever decorative.

Screamer

Loud is not filled. The keyline thickens to 2.5px and takes full ink on both the edge and the label. It is still the same box — which is the point of the family.

Buttons

Menu

States

rest hover held focus chosen

Editable

Turn any page into a booking portal Track a no-show without asking who joined Reassign a booking when someone calls out

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. Nothing separates a resting editable line from a divider under a heading, so the family is only honest in a document that never draws a decorative rule. In exchange it is the cheapest thing here: src/button.css is already this box, and the editable mark is four declarations on top of it.

02

Lozenge

A ground, not an edge. No hairline anywhere in the family. An action is a filled pill; an editable is the same ground with the fill taken almost all the way out — a wash at 5% that comes up to 12% as you approach. The wash takes the constant corner rather than the pill, because a wash wraps and a pill's radius would grow with it. Highlighter instead of rule, so the mark sits behind the words and never competes with the type's own baseline furniture.

Screamer

The nearest thing to the real hero pill: signal ground, radius half the height.

Buttons

Menu

States

rest hover held focus chosen

Editable

Turn any page into a booking portal Track a no-show without asking who joined Reassign a booking when someone calls out

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. Width. The padding rule is not optional on a pill, and four icon buttons in a row become a scroll. The resting wash is the real risk: at 5% on --surface it is almost invisible, so this family needs the editable text to sit on --bg, and it needs testing on a screen that is not the one it was drawn on. It also has the strongest primary in the set — one signal pill in a view and nothing else competes.

03

Rule

Graduated 2026-10-08 as the house secondary, .wm-rule in src/button-sets.css — system page, The corner law, “The button”.

The type is the control. No box in any state. An action is ink-full with a solid rule at 1px, thickening to the same two numbers the press law uses — 1.2 under the pointer, 1.8 held. An editable is the identical rule, dashed. Solid means it happens; dashed means it is yours. This is the one family that can be wrong in the exact way the brief names, and the condition is absolute: nothing else in the document may be underlined.

Screamer

No box at any size. The rule thickens with the type instead of staying 1px.

Buttons

No icon-only form. There is nothing to draw it in. Those actions borrow 01.

Menu

And this is where the family runs out. The rule scopes to the text, so in a row the leading mark, the label, and the trailing mark are three objects at the same height with nothing holding them together — an underline cannot reach across a flex gap, and the row never becomes one thing. A box does that for free, which is the argument for a box. Widening the rule to span the whole row does bind it, and then it is a divider: the one mark this family has already means "press", so it cannot also mean "these belong together".

States

rest hover held focus chosen

Editable

Turn any page into a booking portal Track a no-show without asking who joined Reassign a booking when someone calls out

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. Three things. The 44px floor is bought entirely with invisible padding, so two stacked actions are 88px of line box or they overlap. Two actions side by side read as one sentence unless something separates them. And the whole family depends on a document-wide prohibition that no component can enforce — the first underlined caption someone adds takes the affordance down with it.

04

Plate

Hills and holes. Raised is press, recessed is type — and nobody has to be taught it. One lightness ramp does all of it: the plate is +L on the ground with the light from above, the well is −L with the light inside, and there is no hue and no second palette anywhere. Held, the plate goes into the ground rather than changing color.

Screamer

Still a plate, not a fill. The lift stays a 1px highlight and the drop goes from 2px to 6px: the hill gets taller rather than becoming a color.

Buttons

Menu

States

rest hover held focus chosen

Editable

Turn any page into a booking portal Track a no-show without asking who joined Reassign a booking when someone calls out

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. The corner law already records the fault, and this family is built on the thing that has it: an inset box-shadow follows border-radius but not corner-shape, so every well and every held plate paints a rounded rectangle inside a superellipse, visible at the corners. That is not a bug to fix here — it is the engine — so choosing 04 means either keeping the radius small enough to hide it or giving up the house corner on exactly the controls that carry it most. It is also the only family that reads as a different decade.

05

Bracket

Corner ticks that close on approach. The box is there; only its corners are inked. --bt-tick is how far the ink runs along each edge, and hover animates it to 50% — at which point the four ticks meet and the enclosure completes. The ticks are a real border under two intersected masks, so they carry the G2 curve rather than imitating it. Editable text keeps only the left and right pair: brackets around the words, the way a field is marked on a drawing.

Screamer

The ticks get heavier instead of surrendering. 3px, full ink, run out to 26px — the family's own device at display scale rather than a borrowed fill.

Buttons

Menu

States

rest hover held focus chosen

Editable

Turn any page into a booking portal Track a no-show without asking who joined Reassign a booking when someone calls out

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. mask-composite: intersect is the whole mechanism; where it is missing the mask collapses and the full border paints, which is family 01 — a correct button, so the degradation is honest, but a family whose fallback is another family is one design being maintained as two. At UI size the tick is 9px and four of them in a dense menu read as noise rather than as an edge. The transition needs --bt-tick registered with @property, or the ticks snap.

06

Menu

Graduated 2026-10-08 as the house primary, .wm-menu in src/button-sets.css — system page, The corner law, “The button”.

An enclosure means chosen, not pressable. The inversion of the five above, and the only one already running: it is what font-proofer's rail already does, and what that is, is a menu — preview modes of equal weight, picked between rather than read, which is the one place on this page where a mark earns its space. A row has no edge at rest — a mark, a label, and the full width of the column as its target — and the ground arrives when it is the one that is on. The editable mark is not a proposal either: it is .slider-number, the rule the dial ships. An underline drawn on the text, not a border drawn on the box — exactly as wide as the value, at the baseline rather than under the descender, and focus takes the digits and the rule together.

Screamer

And here is the family's ceiling: chosen IS the loudest thing it owns.

Buttons

The primary is the giveaway: 06 spends its enclosure on chosen, so the one action that matters can only borrow the same ground. There is no louder left to go.

Menu

States

rest hover held focus chosen

Editable

measure 620px
size 18
tracking 0.000

In situ

Headline20 ≤ char
Ours. Always Cal Sans at Base.

What it costs. A resting row states nothing on its own — which is the brief’s failure exactly, and the family survives it only on two conditions it cannot enforce for itself: every row carries a mark, and every row is the full width of its column. Strip either and this becomes the unformatted list. It also needs somewhere to be a menu: the row shape wants 200px and a left edge to sit against, so an action in the middle of a paragraph has to borrow another family. And because the enclosure is spent on chosen, there is nothing left to spend on primary — a destructive action here looks like every other row.

And it does not do prose. Visible in the situ card above: the rule is drawn on the text, so on a value it is four characters wide and on a wrapped sentence it is two full lines of underline. .slider-number was written for a number in a rail and it is right there. The editable case in the brief is a headline, not a value — so if 06 wins the menu, something else still has to win the paragraph, and the two have to look like one decision.

What each one demonstrates

All six draw the same six specimens, in the same order, on the same content — screamer, buttons, menu, states, editable, in situ — so a column means one thing all the way down the page and a difference between two cells is a difference between designs. Where a cell is empty the family cannot do that thing, and that is the finding — not an omission.

familyscreamerbuttonsmenu5 states editablein situcannot do
01 Keyline ✓✓✓ ✓✓✓ —
02 Lozenge ✓✓✓ ✓✓✓ —
03 Rule ✓✓✓ ✓✓✓ an icon-only button — nothing to draw it in; and a row — the rule scopes to the text, so a leading mark, a label, and a trailing mark never become one object
04 Plate ✓✓✓ ✓✓✓ the house corner on its own wells — inset shadow follows border-radius, not corner-shape
05 Bracket ✓✓✓ ✓✓✓ — (its cost is legibility: 9px ticks read as noise in a dense menu, not as an edge)
06 Menu ✓✓✓ ✓✓✓ a primary louder than chosen, and an editable that is prose rather than a value

The screamer is not the UI button made bigger, and that is why it earns a row. The menu runs at opsz 10; Cal Sans's axis is 8 – 14 – 45 and a display line belongs at the top of it. Scaling a 13px button to 34px and leaving the optical size alone prints the small drawing large — thicker stems, looser fit — which is mis-drawn rather than merely big. Every screamer here runs opsz 45 and GEOM 50, which is what the real hero pill runs.

Weight is not part of “display”, though. The hero pill is also wght 700, and copying that to all six was a second emphasis stacked on whatever the enclosure was already doing — the rule icon.css states for the mark: once one thing carries the emphasis, a heavier stroke on top is two emphases stacked. A 2.5px keyline, a 0.1em rule, 3px ticks, and a raised plate are each loud by themselves, so their type stays at 400, where Cal Sans is drawn. 02 keeps 700, and for a legibility reason rather than a volume one: it is the only screamer that inverts — dark ink on a signal field — and weight there is holding the counters open against a flat color.

It reads tight on top because the bottom is carrying space nothing occupies. The face is left alone — no axis is touched. With line-height: 1 the baseline sits 0.8275em down, the cap top 0.1075em down, and the descender falls 0.0650em past the bottom of the line box; the hero pill then adds 0.38em of padding below against 0.22em above, which leaves 0.315em of clear air under the descender and a top gap that is mostly empty line box. text-box-trim removes that room rather than balancing it: trimmed to cap and baseline the box is the lettering itself, symmetric padding becomes optical centering by construction, and the descender hangs with 0.1025em to spare. The pill comes down from 1.70em to 1.40em and the type sits in the middle of it.

What none of the six demonstrate, and would have to be decided separately: a pending or loading state; a destructive action that has to look dangerous without a second hue; a button label that wraps to two lines; a count or badge riding the enclosure; a split button; and an HDR chosen state on an enclosure rather than on a mark — the row below shows the mechanism, and no family assumes it.

One swatch, two ways to cut it

Not a seventh family — a mechanism, and one that already ships. Active is the only state that leaves SDR. The ladder spends everything getting to chosen — faint to quiet to full, GRAD 50 to 100 to 150 — and then has nowhere left to go on a screen that can go further. So the chosen mark is painted with a flat PQ white instead of with ink. src/icon.css does it to the mark; src/AxisSlider.css and src/AxisTriplet.css do it to the stepper chevron. font-proofer's own source carries none of it — it arrives through shared/ — so there is nothing here that is a port. The swatch below was read out of icon.css when this page was written, not retyped, and it is byte-identical in all three primitives.

The swatch

sRGB #fff
PQ

One pixel of field-247, its brightest — about 1216 nits through the PQ EOTF, and literally the same light the poster puts on its headline. Deliberately not PQ 1.0: that is 10,000 nits, which no display resolves, so the system tone-maps it — and clipping is where a bright white turns chalky and shifts hue. Sampling the field's own peak also means a mark glows at exactly the wordmark's white rather than outshining the logotype from 20px. If these two squares are the same color, your screen is SDR and everything below will read as plain full ink.

Cut A — the glyph is the mask

format_paragraph0 format_paragraph0.5 format_paragraph1 grid_viewlive 1.00

color: transparent plus background-clip: text makes the engine rasterize the letterform as a mask rather than as smoothed colored text. That is also why a chosen mark reads heavier than its GRAD implies — a mask is full grayscale coverage, more actual pixels, not bloom. Two layers make the boost a slope: ink on top at alpha (1 − hdr), HDR white underneath. At 0 the ink is opaque and the mark looks exactly as it did, which is why the handover is invisible. The paint itself cannot be interpolated, so only that one registered number animates.

Cut B — a shape is the mask

hold it

Same swatch, different cut: here it is the paint, and an SVG chevron masks it — so the thing that lights up is the thing you pressed, rather than a second shape appearing in a 10×6 box that already holds a chevron. Momentary is two durations, not an animation: opacity snaps on at :active with duration 0 and eases back out, so the flash cannot lag the press or be re-triggered mid-decay by a repeat from the hold timer. The mask is drawn at the chevron's hover stroke, because a press almost always arrives through a hover and a mask thinner than the stroke under it leaves a rim of ink around the light.

The light gate

format_paragraph
chosen, on a light ground

On a light ground the whole thing is unwound: clip back to border-box so the glyph is colored text again and not a mask, the number to 0, the range limit back to standard. Physics rather than taste — the boost exists only for pixels brighter than SDR white, and there is no darker-than-black to boost into. Left ungated, the swatch painted the chosen mark white on a light page: the faintest thing in the row instead of the fullest, which is the exact inverse of what active means.

What this does not settle. It is a technique for marks. Every family above decides a chosen state in ink, ground or edge, and none of them assumes a screen that goes past white. Putting an HDR chosen state on a button enclosure is a design question in its own right, not a port of this — so it is shown here and deliberately left out of all six.

Which one

One column matters more than the others. At rest is whether the thing announces itself with nobody pointing at anything — which is the property the brief asks for, and the one a hover state cannot supply.

familyat resticon-onlydense menu 44pxno corner-shapeeditable mark
01 Keyline states itselfyestight paddingplain radiusone edge of the box
02 Lozenge states itselfyeswide nativepill is a pilla wash at 5%
03 Rule only if nothing else is underlinednone reads as proseinvisible padding unaffectedthe same rule, dashed
04 Plate strongestyestight paddingshadow breaks the corner a well: the hole
05 Bracket quiet but unmistakableyes 9px ticks read as noisepadding degrades to 01brackets around the words
06 Menu nothing, unless the row keeps its mark and its width bare markit IS the menu row heightgrounds, not corners the rule the dial ships

Two of the six are already half-built. 06’s editable mark is not a candidate at all — it is .slider-number, shipping, in every dial in every app, and choosing anything else for editable text means the menu and the field disagree inside one panel. 01’s enclosure is src/button.css, likewise shipping. Those two fit together without being reconciled: 06 for the menu, where an enclosure means chosen and the row does the pointing, and 01 for everything outside it, where an enclosure means pressable. What that pairing has to survive is one panel holding both meanings of a box at once, which is a real risk and is the thing to test before it is adopted rather than after.

02 keeps the two jobs it already has — the one primary action, and one-of-N — as a variant, not a rival. 04 is the strongest at rest and is blocked by the engine, not by taste. 05 is the one worth keeping drawn for a hero or an empty state, where a single button can afford ceremony a dense menu cannot. 03 is the one to reject on purpose rather than by omission: it is the most typographic answer here and it fails exactly where the brief says not to.