Icon button

Level 1, and the second largest control family: 1,361 placements on 106 painted screens. A control whose whole content is a mark. It was drawn in six files under six names until 2026-08-06 and it is one file now, with eight faces, and every one of them is an answer to the GROUND it stands on rather than a variation on a circle. Measured 2026-08-08.

1,361 placements 8 faces 763 buttons, 598 links 1 face takes the floor

On the ground it answers

The lift face on the footer's social row, which is the largest of the eight at 525 placements.

Vault, dark
Daylight, light

What this page is for

Same reason as button: a level is a shelf and a component is a page. This family is 1,361 placements over eight faces, and a shelf has room for one specimen. The eight are not sizes of one thing, so showing one of them teaches almost nothing about the other seven.

The thing this page can show and the level page cannot is the ONE sentence that organises the whole component: a face here is an answer to the ground it stands on. Read the eight side by side and that stops being a claim; the circle is the header band's, the tile is a photograph's corner, the bare mark is a caption row, and the small one is a toast. Read one of them alone and it is just a padding.

The declaration that this is level 1 stays on the atoms page and is not repeated here.

The eight faces, and the ground each one answers

the circle, 137 placements, and every one is in the header

Vault, darkDaylight, light

The circle is the header band's skin, not this atom's own idea, and counting says so: all 137 plain .icon-btn in the product stand in <header>, all of them in the utility group, and there is no plain circle anywhere else. It answers a pointer the way the band's other three controls do.

Two marks, and it was three until 2026-08-14 and one until 2026-08-09. Read from the screens rather than from this page: the bell on 32 and the bookmark on 105, which is 32 + 105 = 137, the number the line above already carried. It was 242, and the 105 that left were the left group's hamburger: a <button> labelled "Menu (reserved for future scaling)" with no handler, no drawer and no destination, display:none below the desk on top of that. It went from 105 painted screens, 87 grey ones and 10 specimens here on 2026-08-14, and its glyph left the sprite with it. A count can be right while the picture under it is one third of the thing. The third one carries .desk-only, a `base.css` utility that is display:none!important below the breakpoint, so on a phone the header's Favorites circle is not there at all: it is the bar's Favorites slot instead. That is a fact about the atom's placements, so it is drawn here rather than described.

bal-swap and bal-add, 73 each, and 0 of the 146 render at 390

Vault, darkDaylight, light

The balance cluster: two circles at a denser size beside a figure. Their 73 screens each are all desk, because the cluster is .desk-only and base.css hides it below 640. A face measured only at 1280 is still a face; a placement count that does not say where it renders reads as coverage.

photo, 228 + 105 with the strong ring, and all 333 are a sheet close

Vault, darkDaylight, light

The specimen stands on a photograph and not on the plate, which is the only honest ground for it: it reads --scrim-photo, --line-on-photo and --text-on-photo, three roles for standing on something the system cannot choose the colour of. Its scrim is 34 per cent at rest, 55 under a pointer and 72 held down, so the mark stays legible whatever is underneath. The image is one of the three things the rules allow on the element, and it is here for the same reason it is on the atoms page: a face defined by an unpredictable ground cannot be shown on a predictable one.

And it is named for a ground it does not stand on today. All 333 placements are .sheet-close inside a <dialog>, and what is actually behind them is .sheet-head, which paints a brass radial gradient and not a photograph. The name is kept on purpose and components/iconbtn.css says why: what defines the face is the three on-photo roles it reads, and a dialog head is the same problem, a ground the control cannot predict. It is written here rather than left as a smell, because the next person to meet the class will read the name before the comment.

.icon-btn-ring-strong stood here until 2026-08-11 and it is gone. It was a modifier on 105 of these discs, the How-it-works sheet's, changing exactly one thing: the focus ring went white, because .hiw-glow put a brass radial in the corner the ring lands in and the system's brass ring measured 2.52:1 there. Re-measured after the glow became one rule with the plain sheet head's, at the button's own corner in the annulus the ring covers: the brass ring is 2.93:1 on the How-it-works head and 2.89:1 on the PLAIN sheet head, so the head carrying the exception was the better of the two by 0.04 and the 222 that were not carrying it were worse. A class on 105 of 333 discs is a list, and this system has already paid for a list. It is one rule keyed to the family now, in components/dialog.css beside the placement, for the reason course-chrome.css writes over its own ring: a ring answers to what it stands on, and what these 333 stand on is a sheet head.

THE SAME DISC WAS ALSO LOSING ITS CIRCLE THE MOMENT IT TOOK FOCUS, and it was not this component doing it. Backlog 111, found by the measurement above and fixed on 2026-08-11. On the 17 standalone overlay pages this control is an <a> rather than a <button>, because there it navigates instead of closing, and dialog.app-dialog a:focus-visible in dialog.css set border-radius:var(--radius-6) on every anchor in a sheet. An outline follows its element's radius, so rounding a ring means rounding the element, and at (0,2,1) that rule beat .icon-btn's pill: the disc was a 32px circle at rest and a 6px-cornered square while focused, on 17 pages, and showModal() autofocuses it, which makes it the first paint rather than a rare state. A component cannot out-specify a rule about the box it stands in, which is the second time this week the fix belonged to the container's file and not to this one. It is scoped to a:not([class]) there now, and all 48 shaped anchors across those pages keep their own shape.

lift, 525 placements, the largest face in the family

Vault, darkDaylight, light

The footer's social row, five marks on every one of 105 screens. It is an <a>, and this is the face that makes the family's tag count what it is: 598 of the 1,361 placements are links rather than buttons. The atom declares text-decoration:none and text-align:center for exactly this reason, and the file records that it did not until 2026-08-07, when Favorites became an anchor and picked up an underline nobody had chosen.

bare, 84 placements on 12 screens, and its target is invisible

Vault, darkDaylight, light

The save mark on a card's caption row, where a circle would be a second object competing with the picture. It is the only face that answers WCAG 2.5.5 outright and it does it without a box: 44 of target with no ground and no edge, pulled back out of the flow with a negative margin so the row is not spread by it. What answers is the mark, quiet at rest, brass under a pointer, and filled brass when the event is saved, which is a state and not a press.

tile, 27 placements on 9 screens, and the only 10px corner in the family

Vault, darkDaylight, light

Three of them sit in a row over the event head, and the corner is the whole argument: three circles read as a control group, three tiles read as a toolbar. It is the only face with a ground of its own on an ordinary surface, --bg-control, because the head behind it is a photograph and a transparent tile would vanish into it.

small, 4 placements on 1 screen

Vault, darkDaylight, light

The toast's dismiss, and it is the circle at 24 rather than a face of its own: transparent, hairline, pill, exactly the header's. It disagreed on one thing, its hover, which brightened the EDGE where the header's tints the GROUND. Four placements against 137, so the header's was the system's and this was the divergence, and the divergence was corrected rather than carried into the atom.

THIS PARAGRAPH SAID "THE DEFECT" AND IT WAS WRONG TWICE, so it is kept and corrected rather than deleted. It read: "this is the one control in the family with no mark in it, and the mark it is missing is a letter ... the box is 24x24, it holds 0 svg, and its content is the text character x, with nothing on ::before or ::after ... 333 close controls in this same family draw a real cross ... one job, two drawings. A lowercase letter is not a cross."

THE FIRST ERROR WAS THE PREMISE. iconbtn.css draws the cross with two rotated bars on ::before and ::after, and the button is font-size:0, so the letter draws nothing here. It was removed on that reading and the measurement said no: in the grey tree the letter is the whole drawing, because wireframes/ styles this as a bordered box at font-size 11 with no pseudo-element, and the emptied button measured 16 x 8 with nothing in it. The same markup is inert in one tree and load-bearing in the other, which no reading of one tree could find.

THE SECOND ERROR WAS "ONE JOB, TWO DRAWINGS", and the answer is a number. Measured 2026-08-10: the stroked path is a 12-unit cross in a 24 viewBox at stroke 2.2, rendered in a 16px svg inside a 32px button, so its ink spans 9.47px, 29.6% of the button. The two bars are 8 x 2 rotated 45 degrees in a 24px button, so their ink spans 7.07px, 29.5%. Two techniques, one mark, the same optical size to within half a per cent, and the technique differs because one control carries an svg and this one carries a letter the grey tree needs. It is a decision, not a divergence. What WAS a divergence was in the kit: four specimens on the toast page put an svg cross inside this button and drew the mark twice, brass over grey, and six on the vitrine shipped it empty. Backlog 75 and 88, closed 2026-08-10.

What each face renders, and the one that moves

Read at 390 in both pointer branches on 2026-08-08, with every dialog forced open so the sheet close could be measured at all.

facewornfine pointercoarse2.5.8 AA, 24x24
.icon-btn, the circle13736x3644x44clears
.bal-swap / .bal-add73 / 73desk only, and 0 of the 146 render at 390clears at 1280
.icon-btn-photo22832x3232x32clears
.icon-btn-photo on a sheet head33332x3232x32clears
.icon-btn-lift0, was 52528x2828x28clears
.icon-btn-bare8444x4444x44clears
.icon-btn-tile2728x2828x28clears
.icon-btn-small424x2424x24clears, exactly

ONE FACE OF THE EIGHT MOVES WHEN THE POINTER CHANGES, and that is a finding rather than a fact about this page. The touch floor in components/base.css excludes four of them by name, .icon-btn-photo, .icon-btn-tile, .icon-btn-small and .icon-btn-lift, and the exclusions carry their reason: the floor would resize all four. .icon-btn-bare needs no exclusion because it sets the floor itself. So the circle is the only one the rule reaches. Every face clears WCAG 2.5.8's 24x24 and four of the eight do not clear this project's own 44, which is backlog 39 and is open on purpose: whether a dismiss on an overlay, a corner toolbar or a footer mark should be 44 is a design question, and a migration is not the place to answer it quietly.

The 525 in that row is 0 since 2026-08-13, and the face is kept on purpose. .icon-btn-lift was the footer's row of five social marks on 105 screens, and backlog 144 took them out: five anchors a screen at href="#" for accounts that do not exist, standing directly under the trust strip. A social account can never become an internal route, so it was not a link waiting for a screen, which is what separated it from the two kinds of placeholder the footer's own rules already covered. The face survives its placements and account.css did not, and the difference is the right one: that file was deleted for having no FACE left, its two rules being another component's stone; this is a face with no placements, and its placements return on a date somebody chooses. It is the only entry in this table worn by nothing, and the row says so rather than reading as a face nobody measured.

The sentence the eight faces are for

A face here is an answer to the GROUND, not a variation on a circle. That was not the plan, it is what reading them out loud produced, and it is the thing worth carrying to the atoms still to migrate.

groundthe answerface
the header banda circle that tints its ground, because the band already has a skinplain, and the dense pair
something the system cannot coloura scrim that deepens in three steps, and an on-photo inkphoto
a corner of a photograph, three in a rowa 10px tile with a real ground, because a transparent one would vanishtile
a caption row beside a pictureno box at all, and the mark answers insteadbare
the footer's quiet banda small mark that liftslift
a toastthe header's circle at 24small

Six grounds, eight faces, and the two extra are a modifier that changes one value each: the balance pair is the circle at a denser padding, and the strong ring is the photo face with a white focus outline for one head. Neither is a face and both are worth having a name, because the alternative was a second declaration of the whole control in the file that happened to hold it.

States

As on the button page: a hover, a press and a ring are raised by a person and not by a document. The specimens above are the real controls, so raise them here with a pointer and a Tab.

facehoverpress
the circlethe band's wash, --tint-brass-16, and the action edge, which is what the header's two disclosures and its ghost button also answer with--bg-pressed, square cornered, because a radius is geometry and a state may not move the element it is on
photothe scrim goes 34 to 55 per cent and the edge brightensthe scrim goes to 72
liftthe mark comes up and the control liftssettles back
barethe mark goes brass, and nothing else moves because there is nothing elsethe one time this face draws a shape
tilethe ground steps and the edge goes brass--bg-pressed

The saved bookmark is a STATE and not a press, and it is the one thing in this family that survives the pointer leaving: aria-pressed="true" fills the mark brass. That is why the control is a <button> with an ARIA state rather than a class the page script toggles.

What it does with width

Its own width query: 560 one-off. The disc is fixed at every rung. The one width rule is the TILE face dropping from 36 to 28 below 560, shared with event-detail and with nothing else, so a row of them still fits. It is a one-off and the argument for keeping it is written beside the other half of the pair.

The rule

Pick the face by the ground, not by the size you want. Every one of the eight exists because something underneath it made a demand, and the sizes are what fell out of that. Asking for "the 28px one" is asking for a footer mark in a header.

A mark with no word needs a name. Every placement in this family carries an aria-label, and the audit of 2026-08-08 read 0 nameless controls across 115 documents. A control whose whole content is an svg has nothing for a screen reader to say on its own.

A region may say WHERE one sits and may not say what one looks like. components/header.css keeps .app-header .left > .icon-btn{display:none} because hiding a control in a band is the band's decision, and components/dialog.css keeps the sheet close's absolute placement for the same reason. Neither draws the control.

The anti-rule

Never draw one in the file of whatever holds it. Six files held the pen for this control under six names until 2026-08-06, and every one of them was right about its container and wrong about the control.

Never let a labelled press in. .hiw-btn sat in the census row called "icon only, in the header" while carrying the words How it works on 105 screens. A row that describes WHERE a control sits will collect anything that sits there.

Never add a face for a single element. The strong ring is a modifier and not a ninth face, because it changes one value for one head. A face is a new ground, and there are six.

The second one is the load-bearing half and it cost this component a whole census row. A group named for a place fills up with whatever is in that place: the logo left the same row before the How-it-works pill did, for the same reason. Sort by what a control IS, and an icon button is a press whose entire content is a mark.

Where the rest of it is

The level and its reason: the atoms page. The stylesheet and the argument for every value: components/iconbtn.css, which is where all six kinds arrived on 2026-08-06 and became the first atom on the map to reach a distance of zero. The deleted stand's writing on this component: docs/kit-archive/authored/iconbtn.md. The counts on this page were taken on 2026-08-08 and the file's own comments carry the counts of two days earlier: 908 placements in six files, then 836 adopted, against 1,361 measured today across eight faces. The difference is not drift in the product, it is a wider net: this page counts .bal-swap and .bal-add and every anchor.