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.
The provider buttons as the sign-in screen ships them, which is the one place two emphases and a mark stand in one row.
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.
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.
| class | what it carries | worn |
|---|---|---|
.btn | The 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-secondary | The quiet skin, three colours and nothing else: --bg-control, --border-hairline on the band the base declared, --text-primary, semibold. | 509 |
.btn-primary | The brass skin: the 135deg action gradient, --text-on-brass, bold, band left transparent so the gradient paints straight through it. | 212 |
.btn-ghost | Keeps the box, gives up the fill: transparent on a hairline pill, muted ink. | 105 |
.btn-bare | Gives up the box as well: no ground, no edge, no corner, and its padding is a hit area rather than a size. | 72 |
.btn-prompt | The invitation: --bg-well, a recessed ground, muted ink, semibold. The only emphasis drawn on a well. | 4 |
.btn-block | The control owns its row. The fourth axis, and the only one that is not a look. | 576 |
.btn-provider | The 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-apple | Parts, 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.
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
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
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
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
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
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 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.
| size | padding / font | worn | renders, fine pointer | renders, coarse | where |
|---|---|---|---|---|---|
.btn-xs | 4/8 at 12px | 105 | 0 render at 390 | 44 at 1280 | the header band's own rhythm, and it is .desk-only, so on a phone none of the 105 is on screen at all |
.btn-sm | 8/12 at 12px | 138 | 36, all 138 | 44, all 138 | the header's entries, the action inside a state block, Post under a comment |
.btn-md | 12 at 14px | 579 | 47 | 47, unchanged | the row inside a sheet, the panel, the action bar. By far the most of the product |
.btn-lg | 16 at 14px | 8 | 55 | 55, unchanged | the mobile bet sheet's Confirm and the sign-in prompt |
| none | 4 at 11px, its own | 72 | 25 | 44 | .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.
.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.
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.
| emphasis | hover | press |
|---|---|---|
secondary | the ground steps one stone darker to --bg-control-hover, the edge goes brass, the label lifts to --text-strong | settles onto --bg-pressed, the system's one held-down ground |
primary | the lit brass goes to both stops so the whole face comes up, plus a soft glow under it | the 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 |
bare | the 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 |
prompt | all 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
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.
.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.
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.
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.
| form | scope | n | screens |
|---|---|---|---|
secondary md block provider | a class since 2026-08-08, a scope before it | 322 | 105 |
primary md block | a dialog | 106 | 105 |
ghost xs | the header | 105 | 105 |
secondary md block | a dialog | 105 | 105 |
primary sm | a screen | 78 | 64 |
bare | a screen | 72 | 9 |
secondary sm | a screen | 58 | 47 |
| 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.
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.
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.
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.
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.
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.