Button

Level 1, and the largest control family in the product: 902 placements on 106 painted screens, every one of them wearing .btn. Five emphases, four sizes, one width switch, and 0 scopes left of the eleven this file used to carry. Everything on this page was read off the rendered screens on 2026-08-08 rather than off the stylesheet.

902 placements 5 emphases 4 sizes 22 forms 0 scopes 0 links

The family, on a real row

The provider buttons as the sign-in screen ships them, which is the one place two emphases and a mark stand in one row.

Vault, dark
Daylight, light

What this page is for

A level is a shelf and a component is a page, and the two answer different questions. This was the first page written under that rule, on a day when the rule still said the opposite: a page per level, not per component, because forty pages of one component each are forty navigations to compare two chips. That reason was right and it was a reason for the SHELF. Comparing ten atoms is what vitrine.html is for and it keeps that job; taking one apart needs room a shelf does not have.

This family makes the case in numbers. It paints five emphases against four sizes against a width switch, and the product wears 22 distinct forms of it across 902 placements. A single specimen on the atoms page can show one of the 22. The other 21 are the component, and the two questions a person actually arrives with, which emphasis and which size, cannot be answered by looking at one box.

What is deliberately not repeated here. The declaration that this is level 1 and the reason for it are on the atoms page and stay there. A fact written twice drifts, so this page holds what that one has no room for and points at it for the rest.

Anatomy

Every selector in components/button.css requires .btn, and that is not a style choice. ui-kit/kit.html is frozen provenance and still carries class="btn-primary btn-lg" with its own copy of nine declarations; a bare .btn-primary rule would land on that page at equal specificity and repaint the record of what was once proposed. Its markup has no .btn, so .btn.btn-primary cannot reach it.

classwhat it carriesworn
.btnThe marker and the anatomy every button has: the 10px corner, the body face, box-sizing, the transition, the centring, and a 1px band that is transparent until a skin colours it. Nothing is a button here without it.902
.btn-secondaryThe quiet skin, three colours and nothing else: --bg-control, --border-hairline on the band the base declared, --text-primary, semibold.509
.btn-primaryThe brass skin: the 135deg action gradient, --text-on-brass, bold, band left transparent so the gradient paints straight through it.212
.btn-ghostKeeps the box, gives up the fill: transparent on a hairline pill, muted ink.105
.btn-bareGives up the box as well: no ground, no edge, no corner, and its padding is a hit area rather than a size.72
.btn-promptThe invitation: --bg-well, a recessed ground, muted ink, semibold. The only emphasis drawn on a well.4
.btn-blockThe control owns its row. The fourth axis, and the only one that is not a look.576
.btn-providerThe sixth face, named 2026-08-08. A button that carries somebody else's logotype: it lifts 1px under a pointer, settles back under a finger, and its mark is FILLED rather than stroked. It was written dialog.app-dialog.signin-dialog .btn until that day, so 36 per cent of every button in the product had a face that existed only inside one sheet.322
.prov-google .prov-x .prov-appleParts, and they are on the <svg> and not on the button. Brand marks, filled with the current colour rather than stroked, because a logotype is not an icon. .prov-google carries its own brand colours in the markup and correctly has no rule.107 / 108 / 107

The X mark is worn one more time than the other two, and the odd one is not a mistake: sign-in-provider-conflict.html carries a second X row reading "Continue with X". A count that came out uneven and had a reason is the only kind worth printing.

The five emphases

An emphasis in this family is a ground, an edge, an ink and a weight. Three of the four is a bug, and the file records the time it was: a fifth emphasis that declared no weight inherited the body's 400 and twenty readings moved before the diff named it.

secondary, 509 placements, the most of the product

Vault, darkDaylight, light

Every action that is not the point of its zone. It has a ground and it says you may.

primary, 212 placements, one per zone

Vault, darkDaylight, light

The action the zone exists for, and there is exactly one of those per zone. DESIGN.md states it as the One-Accent Rule and this component is where it is spent: brass on more than the logo, the current chip and the primary action is brass overspent.

ghost, 105 placements, and all 105 stand in the header

Vault, darkDaylight, light

A real step below secondary: secondary has a ground and says you may, this has none and says if you want. This emphasis is the band's, and counting says so: all 105 of them are the How-it-works pill in <header> and there is no ghost anywhere else in the product. That is why its hover wash is a token read by three files rather than a value typed in one.

The specimen drops one class the product carries, .hiw-btn, and that is the whole difference. It has exactly one declaration in the system, display:none below the detail rung in header.css, and no face of any kind: no ground, no ink, no border, no size. Carried onto the stand for fidelity, the only thing it did here was empty the cell on a phone, so the ghost was the one emphasis of five a reader at 390 could not see, from 2026-08-08 to 2026-08-09. A placement is not an emphasis, and this section is about the emphasis.

bare, 72 placements, on 9 screens

Vault, darkDaylight, light

The like and reply pair under a comment. It is the same rest face a navitem wears and it is not one: what separates them is the MEANING, which is what rule 1 of the level map exists for. Reply does a thing; it does not go anywhere.

prompt, 4 placements, on 2 screens

Vault, darkDaylight, light

The only one here that is an invitation rather than an action, and it stands where the thing a signed-out person came for should have been. So it is the only emphasis drawn on a recessed ground and the only one muted at rest, both on purpose: a prompt that shouts is a wall. Four placements is not too few to be a kind, and the reason is that two of the four stand on the same screen saying different things. The map forbids naming a face for a single element; this is the opposite case.

The four sizes, and what they render

The ramp is read off the product rather than invented: each step is the padding the majority already wore. A size is a padding and a font step, not a second control, and the proof is that nothing else in the file changes with it.

Vault, darkDaylight, light
sizepadding / fontwornrenders, fine pointerrenders, coarsewhere
.btn-xs4/8 at 12px1050 render at 39044 at 1280the header band's own rhythm, and it is .desk-only, so on a phone none of the 105 is on screen at all
.btn-sm8/12 at 12px13836, all 13844, all 138the header's entries, the action inside a state block, Post under a comment
.btn-md12 at 14px5794747, unchangedthe row inside a sheet, the panel, the action bar. By far the most of the product
.btn-lg16 at 14px85555, unchangedthe mobile bet sheet's Confirm and the sign-in prompt
none4 at 11px, its own722544.btn-bare carries its own padding, because asking a control with no box for a box is not a size

Two of the five rows move when the pointer changes, and that is the whole effect of the touch floor on this family. md renders 47 and lg 55 already, so the floor never reaches them. sm at 36 and bare at 25 are the 210 boxes it does reach, and since 2026-08-08 the floor is one rule in components/base.css rather than this file's own. xs is the row worth reading twice: 105 placements and not one of them renders at 390, because the control is the header's and the header hides it below 640. A size that exists on a desk and nowhere else is still a size, and a count of placements that does not say so is a count that reads as coverage.

Width, the fourth axis

Vault, darkDaylight, light

.btn-block is worn 576 times of 902, so owning the row is the majority case rather than the exception. It replaced three scope overrides that each put a width back from a different file, and it says what it does where the person placing the control can read it. It is the only one of the four axes that is not a look.

The states, and why a stand cannot show three of them

A hover, a press and a focus ring are raised by a person, not by a page. Every specimen above is at rest, and no arrangement of markup makes a static document show the other three at once. So they are written here and the page says which is which rather than pretending. Raise them with a pointer and a Tab on this page: the specimens are the real controls and they answer.

emphasishoverpress
secondarythe ground steps one stone darker to --bg-control-hover, the edge goes brass, the label lifts to --text-strongsettles onto --bg-pressed, the system's one held-down ground
primarythe lit brass goes to both stops so the whole face comes up, plus a soft glow under itthe gradient turns over to 315deg so the light falls to the bottom right, and the glow goes with it because the glow is the lift
ghost--tint-brass-16 and the action edge, not the quiet hover: a fill appearing out of nothing is a different event from a ground moving--bg-pressed
barethe ink goes brass and nothing else moves, because there is only the mark and the word to answer with--bg-pressed, the one time this control draws a shape at all
promptall three at once, edge, ink and ground, which no other emphasis here does: a single step would leave the one control that is muted at rest looking almost as it was--bg-pressed

disabled, and the product disables exactly one control

Vault, darkDaylight, light

One placement in 902: Add funds in the deposit dialog while the amount is under the minimum. It says so with aria-disabled and not with the attribute, because a native disabled button is not focusable and the reason it is dead has to stay reachable. The rule matches all three spellings so the attribute and the property give one answer, and the hover and press rules carry :not() for it: a dead button neither lights up nor presses.

The sixth face, and it is not an emphasis

.btn-provider keeps btn-secondary: it is not a louder or quieter version of anything, it is the same emphasis carrying somebody else's mark. All 322 wear one form, secondary md block provider, and 0 of them stand outside the sign-in sheet.

Vault, darkDaylight, light

A logotype is not an icon, and that is the whole face: the mark is filled with the current colour rather than stroked, the two monochrome ones take the brand ink, and Google carries its own four colours in the markup and correctly has no rule.

The forms the product actually wears

A form is the set of btn-* classes an element carries times the scope it stands in. Read off the 106 painted screens on 2026-08-08: 22 forms. This is the census the deleted _worn.py used to fail the build over, taken once, by hand, as a report.

formscopenscreens
secondary md block providera class since 2026-08-08, a scope before it322105
primary md blocka dialog106105
ghost xsthe header105105
secondary md blocka dialog105105
primary sma screen7864
barea screen729
secondary sma screen5847
and 15 more, none above 9 placements: the win and loss dialogs' pairs, the bet sheet's Confirm, the prompt, and the six content-width forms

One form is 36 per cent of the component. The provider row is 322 of the 902, on 105 of the 106 screens, because the sign-in sheet is in the markup of every screen. A vocabulary whose largest form is a single repeated row is a vocabulary that has to be right about that row before it is right about anything else, and until 2026-08-08 that row had no name: it was the last scope this file carried.

The combinations the product does not have

Two emphases that take the size ramp, times four sizes, times block or not, is sixteen. The product wears nine of them. The empty cells are named with a verdict rather than left blank, which is the control every declared list in this repository carries.

secondary lg, block or not. The large step exists for one control, the mobile sheet's Confirm, and that control is the brass one. A quiet button at 16 padding would be a secondary action given more room than the primary beside it, which inverts what the sizes are for. Not forbidden, undecided: it needs a place before it needs a rule.

primary lg at content width. The same answer from the other side. The large size exists because a sheet's foot is the full width of the sheet, so a brass control at 16 padding that does not own its row is asking for weight it has nowhere to spend.

ghost, bare and prompt do not take the ramp at all, and that is three more absences with a reason each: ghost is the header band's and wears xs; bare's padding is a hit area; prompt is only ever the full width of the block it stands in.

Eleven scopes, then one, then none

components/button.css had eleven scoped selectors and has 0. Everything that used to be scoped there, the outcome sheet's sixteen re-declarations, the bet panel's three, the dock's two, the sheet's padding and the resolved panel's width, says what the vocabulary now says in a class and is deleted rather than left to be read.

The last one went on 2026-08-08 and it was the biggest form in the family. The provider rows were written dialog.app-dialog.signin-dialog .btn, so 322 buttons, 36 per cent of every button in the product, had a face that existed only inside one sheet and the family had no name for the one thing they do differently. They are .btn-provider now, and what makes it a face rather than a place is the mark: a logotype is not an icon, so it is filled rather than stroked, two of the three are monochrome and take the brand ink, and Google carries its own four colours in the markup.

And it was the one lifted control in the system with no press. Four things in this product lift under a pointer: the card at -3px, the icon button's lift face at -2px, the hero's trust tile at -2px and this at -1px. The other three all put themselves back on :active, and the card's file wrote the reason: a HELD control settles instead of hanging in mid air. This one lifted and stayed lifted for as long as a finger was on it. The press is the rest position restated, and it cost no new value.

And this file writes no .app-case, which is a measurement and not a preference. Of the 559 buttons that stand inside a <dialog> on the 106 screens, 526 are in a dialog that does not carry app-case and 33 are. Scoping this family to .app-case would have needed a second copy of six selectors, which is exactly what the file used to carry. The .btn requirement does that job with no duplicate: eleven scoped selectors became four, and then one.

The rule

Choose an emphasis, a size and a width. That is the whole of it, and it is three decisions where this component had five names and a question about which one a place wanted. .auth-btn, .state-btn, .provider-btn, .confirm-btn and a bare <button> in an action bar were one component under five names, and the proof is what they did not agree on: .provider-btn alone measured seven faces, and nothing in the product predicted which a place got.

One brass action per zone. The One-Accent Rule is spent here.

Two labels in this component are still two names for one thing, and that is a known defect rather than a decision: the funding action is Deposit on My Profile and in the wallet and Add funds in the header and the dialog; the auth entry is Log in in the header and Sign in in the dialog. Both are open in voice/docs/microcopy.md under same-thing. Copy the label from the row there and not from the nearest screen.

The anti-rule

Never dress an outcome as an action. A YES or a NO is yesno, and its LABEL is green or red because those two colours mean an outcome in this product and nothing else. The ground is the same graphite every quiet control stands on, since 2026-08-16: the odds bar carries the outcome weight so the pair does not have to.

Never dress a value as an action. A quiet control that filters, sorts or loads more is a chip, which is one graphite family with a lighter press than this one.

A <button> is a tag and not a role. Reading the tag as the answer is how the deleted stand came to show a tab strip on this component's page.

The last one is the load-bearing half and it is worth the extra sentence. Sort every button-shaped control in the product by what it IS: an action is a press with nothing carried between presses, a selector carries a value and its selected state is the whole point, navigation goes somewhere. Only the actions stand here. A single chip lifted out of its rail is the half that carries no information, because a chip's subject is its group and the chosen one, so a reader learns a padding and nothing about the only thing the control does.

Never draw it again in the file of whatever holds it. That is how the five names happened: each file was right about its container and wrong about the control. components/account.css painted a button of this family for three stages, because the component that owned the BAR was the one holding the pen. That file no longer exists: the rest of it was the bar's stone, and the stone went on 2026-08-08 with the component under it, so the whole of the action bar is one pattern now.

Where the rest of it is

The level and the reason for it: the atoms page. The stylesheet and the argument for every value: components/button.css, which is 80 declarations where it was 30 rules. The writing the deleted stand did on this component, kept because the same measurement should not be taken twice: docs/kit-archive/authored/button.md. Every number on this page was re-measured on 2026-08-08 and several of them had moved: the archive says 710 placements and 505 secondary, and today it is 902 and 509.