The components

Every component in the library, drawn. The specification is _ds/components.json; this page is generated from it, so it cannot say something the spec does not. Each specimen came out of the deck page named under it rather than being written for the gallery, and each one stands on the ground that component was measured to use.

57components 57specified · the rest listed 1carried in from elsewhere 4with no shared CSS yet

the slots21

Short class names in this deck are SLOTS, not components. .n means 'the name line of whatever contains me' and renders at ten sizes because ten components use it. This was measured: 433 uses across 19 containers. Do not rename them and do not specify them as components.

.nthe name line
.cthe caption under the name
.txname and caption grouped, so a trailing slot can be pushed right
.ctthe trailing slot — a chip, a figure, a status, a count
.kthe key of a figure
.vthe value of a figure
.sthe supporting line under a value
.wthe working under a figure — the inputs it was computed from, in one line
.tthe title of a block that speaks
.pthe paragraph of a block that speaks
.actwhat to do now, inside a block that speaks
.athe annotation under an option
.gthe glyph box of a chooser
.lba field's label
.ina field's input
.phan example of the value, inside a field's input and gone as soon as somebody types
.hintthe line under a field
.gothe chevron, when the row opens something
.fone figure of the group that contains it — the tile in a fig3, key above value rather than beside it
.whythe reason something cannot be done, present in the markup and revealed rather than asserted
.dthe date line of whatever contains it

component parts30

A fixed part of exactly one component, with no life outside it. Not a slot — a slot is a role a container gives whatever it holds, and .n means the same thing in nineteen containers. Not a component either: none of these is ever drawn alone or reused. Anatomy may name them. This was called chromeParts for a day, which was too narrow: .th is the same kind of thing and belongs to keep, which is not chrome.

.notchthe device's cutout. On every phone and always first.
.navdockwhere the nav menu is mounted, pinned to the bottom of the phone
.scrimthe dimming under an open dock. An overlay brings its own at .overlay-dim rather than reusing this.
.composerthe always-available chat bar, pinned under the scroll
.brandthe mark in an app bar's leading slot
.stepdotsprogress through a sequence, in an app bar's leading slot instead of the mark
.ththe 56px thumbnail well of a keep. Defined only in 44-what-a-room-collects.html, so a keep drawn on any other page would lose it.
.ctlthe unavailable control inside an inert card, drawn as an OUTLINE pill rather than a filled one — a full-width gray fill with a centered muted label is the drawing of a disabled primary button, which is the pattern inert exists to replace. Label left, the ask right in blue. 6 uses across 5 pages; a real <button> at the 3 that carry a why, and a <span> at the 3 that do not, because a control with no reason to reveal is not operable.
.latethe abnormal-wait sentence inside a cold. One use, and the only text that surface ever carries.
.late-figone side of a late_pair: a label and a figure. 14 instances, 7 free and 7 costed, and never one without the other.
.late-whywhat the costed side of a late_pair is made of, under both figures. On all 7, which the component requires: the slip is never shown without its cause.
.swthe switch of a toggle
.spone node of a spine: its marker and rail, then a date, a title and a supporting line
.artthe photograph of a room, where the room has one
.sheetthe page inside a viewer, at its own aspect ratio
.headsthe column heads of a comparison, one per candidate
.linone compared line of a comparison: the thing, then a cell per candidate
.segkeythe segmented bar's key: a swatch, the label and its figure on one line, one ROW per segment, the figure on the right edge. It was three equal columns with the label stacked over its figure until 2026-09-06. The bar's tones separate the segments and do not carry their meaning, so this is not optional.
.xrail-brandthe mark and the account's name at the head of the desktop rail
.xrail-rowone door in the desktop rail: icon, label, and the unseen dot when there is one
.xrail-groupthe label between the rail's level one and level two. Inserted at an index, not before a named key.
.xrail-footthe foot of the rail, pinned to the bottom. Capture, which is not a door.
.late-heada bare late pair's own title block, at the CardHead step. Belongs to late_pair and is drawn nowhere else — a block with a card gets a card_head instead.
.overlay-dimthe scrim of an overlay. Clickable and never a tab stop: as a real button it is the first focusable node in the layer, so tabbing forward would reach a full-screen Close before the sheet's own title.
.overlay-panelthe sheet itself inside an overlay, anchored to the bottom edge on a phone and to the right edge past the tablet rung
.overlay-headan overlay's title block: an eyebrow, the name of what the sheet is for, a supporting line, and the close control
.overlay-bodyan overlay's scrolling content. The surface ladder resumes here — cards and rows sit on the page ground exactly as they do in a scroll.
.overlay-footan overlay's action block: the action, and at most one way out beside it
.region-heada region's heading: an eyebrow naming the region, an optional block title, and an optional supporting line. Neither eyebrow nor title is a gray band with no explanation, and is checked.
.region-bodya region's contents. Always left-aligned, whatever the heading does — a centered heading over centered options is a poster, and the options are read rather than admired.

type and line primitives10

PRIMITIVES are type and lines. They have a size and a color and no behavior, so they are not components and do not get rules. Anatomy may reference them by class. If a primitive ever grows a rule, it has become a component and moves. THREE ENTRIES ARRIVED HERE FROM THE COMPONENT LIST on 2026-08-04, and the test was this file's own definition rather than taste: every rule on .micro-label, .coach-subtitle and .hero-fig sets type and nothing else — no box, no state, no children of their own — and none of the three had a rule or an anatomy after a day of trying to give them one. hero-fig was the clearest case: it was declared here AND in the component list at the same time, which nothing checked.

.screen-titlethe screen's own title. One per screen.
.t-leada lead sentence, above the body ramp
.t-suba supporting sentence, body ink. .quiet for the variant that sits under a title already carrying the emphasis.
.t-metametadata: a date, a source, a count
.t-numa figure. Tabular numerals, always.
.rulea hairline that fills the width its label leaves. Felt rather than seen.
.hero-figthe one large figure on a surface
.card-titlea card's own title, 21px and always 700. The only thing in the deck at --text-lg.
.micro-label12px uppercase, tracking 0.13em, in the accent. The deck's most-used single class: 291 uses, 176 of them the label of a section_header and 52 the eyebrow of a screen_head.
.coach-subtitlethe one italic in the system, the coach's aside under a title. 21 uses.

chrome9

The frame around a screen. Present on nearly every frame and never the subject of one.

Navigation menu specifiedcarried

.xnav · 185 instances across 35 pages

Rules

  • Closed by default. It is a way to leave, not a place to be.
  • The panel pages. It never scrolls and never shrinks its tiles.
  • Metrics come from the container width, so a phone frame on a desktop page still renders phone metrics.

Carried over

It is the only widget in the app that this product's design actually wants, and it was built and never wired up: zero call sites outside its own folder. The layout math in hc_nav_menu_layout.dart is genuinely good and re-deriving it would be wasted work.

Came across: resolveMetrics(), a direct port of resolveHcNavMenuMetrics, columns from the space available at full tile size; the panel PAGES rather than scrolling or shrinking, two metric sets chosen off the CONTAINER width, not the viewport, the collapsed bar's identity block, per-tile badges, selection, and the full keyboard model

Left behind: the app's theming — the deck drives it from role-colors.css instead, everything about how it is mounted in a Flutter tree, THE BELL. The widget places one in three of its four utility presets and gives it an unread badge; the deck took that default without anybody choosing it, and 34 frames shipped reading "2 unread" over an affordance that opens nothing. It opens nothing on purpose — mockup 22 states three times and mockup 20 once that there is NO INBOX ANYWHERE IN THIS PRODUCT. A reply is the other half of a send, and anything that needs a person waits in Now, which is a door on the same dock. Removed 2026-08-04: notifications is 'off' in every preset in _ds/xnav.js, the dead count attributes are gone from the deck, and the persistent utility is now the avatar alone. The button itself is still built, so a page could opt in with data-xnav-notifications; nothing does. When HcNavMenu is wired up in Flutter its preset needs the same change — porting the bell back is not a fix.

Markup · from 44-what-a-room-collects.html
  <div class="navdock">
    <div data-xnav
         data-xnav-dir="up"
         data-xnav-items="now,house,ask,money"
         data-xnav-selected="house"
         data-xnav-company="HouseChalk"
         data-xnav-initials="SH"
         data-xnav-logout="off"></div>
  </div>

Navigation rail specified

.hc-desk-rail · 10 instances across 9 pages

The desktop navigation. Every door at once, down the left edge, at and above 1024.

Why it is a component

The dock's two levels exist because a phone has no room. A rail has 860 points of it, so the binder stops being a level to descend into and becomes six rows under a label. It is a sibling of the dock rather than a mode on it: the app owns the door list and both render it, and none of the dock's hard parts — open state, paging, drag, the grid — apply to a column.

Rules

  • Every door is shown. The rail does not page, does not scroll and has no second level.
  • The group boundary is an index. The builder's level two starts somewhere else, and a rule that named the first binder key would draw no label at all there.
  • NOT COLLAPSIBLE. The river is capped at 720 either way, so collapsing buys margin rather than content — and costs a second nav state on the surface whose job is answering 'where am I' without being asked.
  • A label never wraps. The rail is 232 because the longest string, 'Record something', measures 129 at DM Sans 14/500.
  • The marker is a dot and never a count. Ruling 4, and the same rule the dock follows.

Not for

  • Below 1024. The dock is the navigation there and nothing about it changes.
  • A collapsed or icon-only state. There is one rail, and it is this one.
Markup · from 19-now-the-week.html
  <div class="hc-desk" style="flex:none;width:1024px;height:540px;position:relative">
    <div data-xrail
         data-xrail-items="now,house,ask,money,papers,people"
         data-xrail-selected="house"
         data-xrail-badges="now:1"
         data-xrail-group-at="3"
         data-xrail-group-label="The binder"
         data-xrail-capture="Record something"></div>
  </div>

Commit bar specified

.hc-desk-commit · 9 instances across 8 pages

The screen's one action, and what doing it will mean, along the foot of the desktop shell.

Why it is a component

The library audit measured 155 solid CTAs and not one of them said what committing does — you read the button, you press it, and the screen tells you afterward. A commit control that cannot be drawn without its consequence is the only version of that rule a component can hold.

Rules

  • The consequence is required. A bar with an action and no sentence is the thing this component exists to stop.
  • One action. The bar is where the screen's single commit lives, so a river underneath it must not carry a second solid cta.
  • At 1024 and above only. Below that the dock holds the bottom edge and ruling 3 settles the collision in the dock's favor; the screen keeps its cta in the river there.
  • The consequence is trimmed at two lines. The bar is a fixed 76, and a consequence needing three lines is one the screen should be saying instead.

Not for

  • A toolbar. It holds one action, not a row of them.
  • A phone. See ruling 3.
Markup · from 19-now-the-week.html
  <div class="hc-desk" style="flex:none;width:1024px;height:76px;position:relative">
    <div class="hc-desk-commit" style="left:0">
      <span class="hc-desk-what">Settling this closes the rough-in · before framing</span>
      <a class="cta" href="#">Settle Island electrical</a>
    </div>
  </div>
Settling this closes the rough-in · before framing Settle Island electrical

Phone frame specified

.phone · 461 instances across 48 pages

The device. A drawing surface for the deck; in the product it is the viewport.

Why it is a component

319 frames rest on it and it has never had a rule. It is also where two questions get settled that nothing else answers: whether a screen has an app bar and whether the dock's room is reserved (164 do, and exactly the 164 that have a dock).

Anatomy

.notchthe device's cutout. First child, always.
.appbaroptional268 of 319. A screen with nothing to put in it does without.
.scrolloptionalthe screen. Present unless a full-frame state has replaced it.
.composeroptionalpinned under the scroll, where the surface is a conversation
.scrimoptionalunder an open dock, and only there
.navdockoptionalthe nav menu's mount. Last child, always.

Rules

  • The notch is on every phone and is always the first child. It is the device rather than the product, and nothing is allowed to sit above it.
  • A SCROLL WITH .nav-reserve AND A .navdock ARE THE SAME DECISION. 187 phones have both and 274 have neither; there is not one of either alone. The reserve exists to leave the dock its room, so adding a dock without it puts the last card under the dock.
  • The app bar is optional and its absence is a choice rather than an omission: 46 phones carry a scroll and no app bar.
  • A full-frame state REPLACES the screen, it does not cover it. The three block phones and the two cold phones contain the notch and that state and nothing else — no app bar, no scroll, no scrim. A state that needs the screen behind it is a scrim over a scroll, which is a different thing.
  • At most one scroll. A phone is one screen.

Not for

  • A desktop or tablet frame. Those lay out in .pic, .rail and .pane inside a .frame-wrap, with no phone around them.
Markup · from 16-the-last-mile.html
  <div class="phone">
    <div class="notch"></div>
    <div class="appbar">
      <div class="brand"><div class="logo logo-mark"></div></div>
      <div class="icon-btn"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg></div>
    </div>
    <div class="scroll nav-reserve">
      <div class="screen-head">
        <div class="screen-title">The last mile</div>
        <p class="t-sub quiet">Things that aren't right. Add whatever you see. The list is yours, and Dave works from it.</p>
      </div>

      <div class="section-header"><span class="micro-label">Open</span><span class="rule"></span><span class="chip chip-count">4</span></div>
      <div class="card">
        <div class="row">
          <span class="tx"><span class="n">Island drawer front sits proud</span><span class="c">Kitchen · added on the walk, 14 July</span></span>
          <span class="ct">With Dave</span>
        </div>
        <div class="row">
          <span class="tx"><span class="n">Paint touch-up, stair wall</span><span class="c">Added 14 July</span></span>
          <span class="ct">With Dave</span>
        </div>
      </div>

      <div class="section-header"><span class="micro-label">Done</span><span class="rule"></span><span class="chip chip-count">7</span></div>
      <div class="card">
        <div class="row">
          <span class="tx"><span class="n">Great room outlet plate crooked</span><span class="c">Fixed 15 July</span></span>
        </div>
      </div>

      <a class="cta" style="margin-top:var(--sp-block)">Add something</a>
    </div>
    <div class="navdock">
      <div data-xnav
           data-xnav-dir="up"
           data-xnav-company="HouseChalk"
           data-xnav-selected="house"
           data-xnav-initials="SH"
           data-xnav-logout="off"></div>
    </div>
  </div>

App bar specified

.appbar · 377 instances across 44 pages

The mark on the left, one or two icon buttons on the right. Never a title — the title is in the scroll.

Why it is a component

Its two slots were never written down. The leading slot is exclusive — no app bar carries both a mark and step dots — and the trailing slot is countable, which is the sort of thing a component either states or re-decides per page. It also went years without naming the back chevron, and because this file listed only the mark and the step dots, the Flutter app was built with no back button at all until a reader audited the frames.

Anatomy

.brandleading optionalthe mark. .stepdots takes this slot during a sequence; when neither is present, the first .icon-btn lands here.
.icon-btntrailing optionalone icon button, or two. Never three. Two or more sit inside a .btn-area, which is the flex row that keeps them side by side with an 8px gap and stops .appbar's own space-between pushing them to opposite ends. A single button may retain that wrapper because the container is the shape the slot has, not something added when a second button arrives.

Rules

  • NEVER A TITLE. The screen's title is in the scroll, inside a screen_head, and an app bar that grows one gives the screen two titles at two sizes.
  • The leading slot holds the mark OR the step dots, never both.
  • Back is a chevron in an .icon-btn, and what decides its side is whether the leading slot is already taken, not depth and not the mark alone. A free leading slot may hold back; the pair never share it.
  • At most two icon buttons, wrapped or not. A third is a menu, and the menu is the dock. Count them as descendants rather than children because .btn-area puts them one level down.
  • Both slots are optional: an app bar may carry only a mark or only a button.

Not for

  • Navigation. The dock is the navigation; the app bar is where the mark and at most two utilities live.
Markup · from 16-the-last-mile.html
  <div class="appbar">
    <div class="brand"><div class="logo logo-mark"></div></div>
    <div class="icon-btn"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg></div>
  </div>

Scroll specified

.scroll · 454 instances across 48 pages

The content column. Owns the gap between blocks; see spacing.

Why it is a component

It is the only thing on a phone that scrolls, and it owns the block rhythm — 243 of the deck's inline margins are gaps between its direct children. Its variants were being written by hand on 175 frames with no statement of what they mean.

Variants

  • .nav-reserve leaves the dock its room. Present on exactly the 187 phones that have a .navdock and on no others.
  • .lnkpage an account-less link surface. 22 instances; no dock, because there is nowhere else to go.
  • .thread a conversation. One instance, and it is the only scroll that reads bottom-up.

Rules

  • The screen scrolls; the app bar and the dock do not. That is what makes them chrome.
  • Its direct children are blocks and the gap between two of them is --sp-block. Anything needing a tighter gap than that is one component with a wrapper, not two blocks.
  • One screen_head, at the top, and only there.
  • .nav-reserve is not decoration and not optional-by-taste — it pairs with the dock exactly. See phone.
Markup · from 21-jobs.html
  <div class="scroll">
    <div class="screen-head">
      <div class="screen-title">Jobs</div>
    </div>

    <div class="card" style="margin-top:var(--sp-block)">
      <div class="row">
        <span class="tx"><span class="n">The Hollis House</span><span class="c">Into the finishes · one decision open</span></span>
        <span class="go"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="color:var(--on-surface-variant)"><path d="m9 18 6-6-6-6"/></svg></span>
      </div>
      <div class="row">
        <span class="tx"><span class="n">Cedar Court</span><span class="c">Inside the walls · inspection the 27th</span></span>
        <span class="go"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="color:var(--on-surface-variant)"><path d="m9 18 6-6-6-6"/></svg></span>
      </div>
    </div>

    <p class="t-meta card-note">Finished jobs stay here. Nothing is archived without you asking.</p>
  </div>
Jobs
The Hollis HouseInto the finishes · one decision open
Cedar CourtInside the walls · inspection the 27th

Finished jobs stay here. Nothing is archived without you asking.

Overlay specified

.overlay · 8 instances across 3 pages

The system's one overlay: something the reader must read or answer without losing their place. Bottom on a phone, side on a desktop.

Why it is a component

The deck had EIGHT hand-rolled overlays under four names on two pages — ob-sheet four times, ob-modal once and ob-side-scrim twice on 04, intake-sheet once on 25 — with three different scrims, two grounds, --r-lg against 24px, no height cap on any of them and not one close control between them: the way out was drawn nowhere except as the dimming. SIX ARE THIS NOW: the four phone sheets and the centered ob-modal on 04, and the correction sheet on 25. The ob-modal was the deck's one dialog, and a dialog is a sheet that forgot which edge it came from. The two ob-side-scrim panels are already side-anchored with a scrim and a way out, so what they need is the deck's desktop frames to become desk containers rather than a rule fix.

Anatomy

.overlay-dimdimthe scrim, which says the thing behind is still there and still yours to return to
.overlay-panelpanelthe sheet itself, anchored to an edge
.overlay-headheadthe eyebrow, the name of what the sheet is for, a supporting line and the close control
.overlay-bodybodywhat there is to read or answer
.overlay-footfoot optionalthe action, and at most one way out beside it. A sheet that is only read has none.

Rules

  • One overlay, and this is it. Pick by what the reader can afford to lose: they can afford to lose their place, it is a screen; they cannot afford to pause at all, it is a say, which sits above the content and never over it; they need to come straight back to what is behind, it is this.
  • It replaces no rung of the surface ladder. It sits above the ladder, temporarily, and the ladder resumes inside it.
  • It caps its own height at 88%. A sheet that can reach the top edge is a screen wearing a scrim, and the sliver of the surface behind it is what says the reader has not gone anywhere.
  • The scrim is clickable and is not a tab stop. The close control and Escape are the keyboard's routes out, and the way out is drawn — the four sheets converted from ob-sheet had a grab handle and no close control at all.
  • The scrim darkens in both themes. Sheet.jsx mixes 32% of --on-surface, which is #F7F7F8 in dark and lights the receding page instead of dimming it; the deck's measured value is used and the package defect is Q192.
  • Never nested, and it carries no navigation. A sheet opened from a sheet is a flow that should have been a screen.
  • There is no dropdown and no dialog in this system. A dropdown hides the options it is choosing between, and this product's argument is that options get compared side by side. A dialog is a sheet that forgot which edge it came from.
  • It has an accessible name, and the behavior comes with the claim. aria-modal is a claim about the whole page: focus moves in on open, Tab is contained, Escape closes, focus returns to whatever opened it, and the title names the dialog. A claim the keyboard does not keep is worse than no claim.
  • overlay is the class, and page 04 meant something else by it: three desktop frames carried class="ob-desktop overlay" from before this component existed. They rendered correctly only because two classes out-specify one and the page's own block loads after components.css — an accident rather than a design. Renamed to ob-overlaid 2026-09-06.
Markup · from 25-first-run-and-setup.html
  <div class="scroll" style="padding-top:52px">
    <div class="screen-head">
      <div class="screen-title">Setup</div>
    </div>
    <div class="card" style="margin-top:var(--sp-block)">
      <div class="row"><span class="tx"><span class="n">Budget scan.pdf</span><span class="c">We could not read this file. Try a different copy, or retry it.</span></span><span class="ct"><span class="chip chip-status is-open">Couldn’t read</span></span></div>
      <div class="row"><span class="tx"><span class="n">March estimate.pdf</span><span class="c">62 lines read as a project budget</span></span><span class="ct"><span class="chip chip-status is-settled">Read</span></span></div>
    </div>
  </div>
  <div class="overlay">
    <div class="overlay-dim"></div>
    <div class="overlay-panel">
      <div class="overlay-head">
        <span class="tx">
          <span class="t">Correct the file type</span>
          <p class="p">Current facts stay in place until a replacement succeeds.</p>
        </span>
        <span class="icon-btn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></span>
      </div>
      <div class="overlay-body">
        <div class="card">
          <div class="row"><span class="tx"><span class="n">Contract</span></span><span class="intake-radio"></span></div>
          <div class="row"><span class="tx"><span class="n">Plans or drawings</span></span><span class="intake-radio on"></span></div>
          <div class="row"><span class="tx"><span class="n">Quote</span></span><span class="intake-radio"></span></div>
        </div>
      </div>
      <div class="overlay-foot">
        <span class="cta">Use this type</span>
      </div>
    </div>
  </div>
Setup
Budget scan.pdfWe could not read this file. Try a different copy, or retry it.Couldn’t read
March estimate.pdf62 lines read as a project budgetRead
Correct the file type

Current facts stay in place until a replacement succeeds.

Contract
Plans or drawings
Quote
Use this type

Region specified

.region · 7 instances across 2 pages

A region of the screen, not a thing on it. A question and its answers, or a set and its label, grouped without claiming they are an object.

Why it is a component

The surface ladder's middle rung, and it existed nowhere: zero selectors, zero instances, and --r-zone:0px declared and read by nothing, while --surface-container carried the comment 'a zone: a region of the screen, edge to edge' since the palette was written. Which left sixty bare options grouped by five different containers — eighteen in .opts on 07, sixteen straight in a .scroll, seventeen in an unclassed div across six pages, six inside a card or a field. One rung, re-decided five times.

Anatomy

.region-headheadthe eyebrow that names the region, and an optional title and supporting line
.region-bodybodywhat belongs together — options, prose, figures or a rail, directly

Variants

  • .region-left the heading reads from the left. The contents are left-aligned either way.

Rules

  • Full width, square and flat. Those three are what tell a region from a card at a glance: a card is inset, rounded and raised. Give a region a radius and it becomes a very large card, which is the mistake it exists to prevent.
  • A card says 'here is an object you can act on or cite', so wrapping a question and its answers in one claims the question is an object. A region claims nothing; it says only that everything inside belongs together.
  • Ground to region to card is the deepest legal nesting. A region inside a region is not.
  • The title is a block title — --text-lg at 700, the same step as a card head — and it names the region and nothing else. Bold at 21px carries more ink than the screen title's regular 27px, so a supporting fact parked there outranks the question the screen exists to ask.
  • The eyebrow alone is a complete region. What is not allowed is neither eyebrow nor title, which is a gray band with no explanation.
  • It runs to the screen edge, by canceling the scroll's gutter and putting it back inside.
  • The package calls this Zone. It is region on both sides because hc_domain already exports a Zone — a room or area of the house — and the package's own first words for it are 'a region of the screen'. Ruled 2026-09-06.
Markup · from 07-one-question-at-a-time.html
  <div class="region region-left">
    <div class="region-head"><span class="micro-label">Pick one</span></div>
    <div class="region-body">
      <div class="opt"><p class="n">Reheat and go</p></div>
      <div class="opt"><p class="n">Simple meals, most nights</p></div>
      <div class="opt" style="margin-bottom:0;"><p class="n">Most nights, from scratch</p></div>
    </div>
  </div>
Pick one

Reheat and go

Simple meals, most nights

Most nights, from scratch

Bulk bar specified

.dc-bulkbar · 0 instances across 0 pages

An always-present selection summary and scope-specific action.

Why it is a component

Ported from the reviewed design-system JSX, props and prompt; decision controls is the first product consumer.

Rules

  • Empty selection shows an instruction without an inactive action.
  • The action names its scope, with clear alongside.
  • The caller docks it without covering content.
Markup · from 50-decision-controls.html · BulkBar
<footer class="dc-footer dc-bulkbar"><div><p class="dc-label">3 groups selected</p><p class="dc-caption">Individual exceptions stay.</p></div><div class="dc-footer-actions"><button type="button" class="cta dc-primary">Open 3 groups</button><button class="dc-textbtn" type="button">Clear</button></div></footer>

header3

Names the screen. One per screen, at the top of the scroll.

Screen header specified

.screen-head · 404 instances across 48 pages

Names the screen: an optional eyebrow, a title, and an optional supporting line. One per screen.

Why it is a component

It is on 134 frames and has never been a component. It exists today as three loose siblings with hand-typed gaps, and its two internal gaps are drawn at nine different values across the deck — eyebrow-to-title at 5, 6 and 7px; title-to-supporting-line at 5, 6, 8, 9, 10 and 16px. That is one component being re-decided 119 times.

Anatomy

.micro-labeleyebrow optionalwhere you are — the house, the room, the job
.screen-titletitlewhat this screen is
.t-subsupport optionalone sentence. Never two.

Rules

  • The eyebrow is where you are, never what this is. Kitchen · counter material is an eyebrow; Add an option is a title.
  • The supporting line is one sentence. If it needs two, the second one belongs in a callout or is not needed.
  • The whole header is one block. The gap below it comes from the container, not from the header.
  • It never carries an action. A header that needs a button is a header plus a block.
  • It is a direct child of the scroll. A .screen-head nested inside a card is a card head wearing the wrong component, and the gate refuses it.

Not for

  • A section inside a screen — that is section_header.
  • A card's own title. Three of these were wrongly wrapped during the extraction and put back; see card_head below.
Markup · from 02-the-space-conversation.html
  <div class="screen-head">
    <div class="micro-label">The Hollis House · Kitchen</div>
    <div class="screen-title">I will need the microphone</div>
    <p class="t-sub quiet">Talk about the room the way you would to a friend. I will pull the decisions out.</p>
  </div>
The Hollis House · Kitchen
I will need the microphone

Talk about the room the way you would to a friend. I will pull the decisions out.

Section header specified

.section-header · 379 instances across 45 pages

Divides a scroll into named runs: an uppercase label, centered and unruled, with the air above it doing the dividing.

Anatomy

.micro-labellabel
.ruleoptionalempty; it is a line, and it fills what the label leaves. Only on left.
.chiptrailing optionala count, or a status about the whole section

Rules

  • THE AIR ABOVE IT IS THE DIVIDER, so the header is CENTERED AND UNRULED. A named run is separated by --section-gap, and once that gap is there the rule is redundant decoration.
  • left restores the ruled version, and it is for ONE case: a run whose trailing chip counts the rows beneath it. A trailing that is an action rather than a count stays centered beside the label.
  • flush drops the lead-in for the first header on a screen, where the head above has already provided the air.
  • A trailing chip describes the SECTION, never the first row in it.
Markup · from 16-the-last-mile.html
  <div class="section-header">
    <span class="micro-label">Open</span><span class="rule"></span><span class="chip chip-count">4</span>
  </div>
Open4

Card head specified

.card-head · 21 instances across 5 pages

A title and a supporting line inside a card, rather than at the top of a screen.

Anatomy

.card-titletitlewhat this card is about
.t-subsupport optionalone sentence

Rules

  • Not screen_head, and the difference is not size. A screen header names the screen and there is one; a card head names a card and there can be several on a screen. One component for both makes "one per screen" unenforceable for either.
  • It uses .card-title. A .screen-title carrying its own font-size is this component wearing a style attribute, and the conformance gate now refuses it.
Markup · from 10-now.html
  <div class="card-head">
    <div class="card-title">Range hood filter degrease</div>
    <p class="t-sub quiet">Hot soapy water. Ten minutes, and it's the one that actually matters.</p>
  </div>
Range hood filter degrease

Hot soapy water. Ten minutes, and it's the one that actually matters.

surface3

Holds other things. Has a ground and a padding and no opinion about content.

Card specified

.card · 542 instances across 47 pages

Rung 3 of the surface ladder, and the only rung that lifts. An object you can act on or cite.

Variants

  • .default white. Takes a hairline inset ring — a white card needs the edge.
  • .card-quiet a lighter lift for a card inside a busy run. It sets no background and keeps the ring, so it is a card wearing a quieter shadow. This note read "quoting something somebody else said" until 2026-09-06, which described what the deck used it FOR rather than what it is; six of its instances are quotations and they keep it, because a quotation is a thing you cite.
  • .card-absent flat and quiet, for a thing the app cannot do. IT IS NOT THE PACKAGE'S absent TONE, and that divergence is open: the package draws absent as the outline rung — transparent with an --outline-strong ring — and the deck fills it with --surface-container at the card radius, which is the region's ground wearing a card's shape. Under either reading a sentence does not belong on it: the outline rung is not for a sentence, and a region is a region of the screen rather than a thing on it. 18 of these held one paragraph and nothing else until 2026-09-06.

Rules

  • A card takes a hairline, and there is no other kind of card. card-sage, card-clay and card-lav were removed on 2026-09-05 with the six tokens behind them: the design system has no tinted grounds, and what a tint used to say is now a word.
  • The hairline is an inset ring, never a border — a real border moves the content in by a pixel and reflows every card.
  • A card never sets its own top margin.
  • A card takes the page's own ink. It does not recolor its text to match its ground.
  • IT IS A CLAIM, NOT A CONTAINER. A question and its answers belong in a region, a lone figure belongs on the ground, and a set that is not an object belongs in an outlined block. Prose belongs on the ground in every case — the ladder's own words are "if you cannot name why a thing needs a surface, it belongs here", and the outline rung refuses a sentence outright because a boxed aside reads as an option nobody picked. A door is not prose: if the way in is drawn as a colored paragraph rather than as an affordance, draw the affordance rather than removing the card.
  • One surface change per level, two levels deep at most: ground → card → rows, or ground → region → card. A card wrapping something that is itself structure — a spine, a region — is that structure's container and not a claim about it; four of them were removed on 2026-09-06 and one of those was the deck's only nesting violation.
Markup · from 21-jobs.html
  <div class="card">
    <div class="row">
      <span class="tx"><span class="n">The Hollis House</span><span class="c">Into the finishes · one decision open</span></span>
      <span class="go"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="color:var(--on-surface-variant)"><path d="m9 18 6-6-6-6"/></svg></span>
    </div>
    <div class="row">
      <span class="tx"><span class="n">Alder Street</span><span class="c">Waiting on Tom's cabinet pick · due the 29th</span></span>
      <span class="go"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="color:var(--on-surface-variant)"><path d="m9 18 6-6-6-6"/></svg></span>
    </div>
  </div>
The Hollis HouseInto the finishes · one decision open
Alder StreetWaiting on Tom's cabinet pick · due the 29th

Card note specified

.card-note · 65 instances across 25 pages

The line that explains the card above it.

Why it is a component

Twenty-four instances drawn at SIX gaps — 10, 12, 14, 16, 18 and 20px — for one relationship.

Rules

  • It is a SIBLING that belongs to the card, not a slot. It sits on the page ground, outside the card padding, so it cannot be inside; but it hugs the thing it is about, so it is not an independent block either. That is why it is an adjacent-sibling rule rather than a wrapper.
  • It takes --sp-snug. Give it the block rhythm and it stops reading as a footnote and starts reading as the next paragraph.
  • This is what the block rhythm was waiting for: a container gap on .scroll would have given all 24 of these 22px.
Markup · from 23-builder-money.html
  <div class="card">
    <div class="row">
      <span class="tx"><span class="n">Committed against contract</span><span class="c">From QuickBooks, read this morning</span></span>
      <span class="ct"><span class="chip chip-money t-num">$486,200</span></span>
    </div>
  </div>
  <p class="t-meta card-note">Draft-and-confirm is for evidence that came from somebody else. What you saw yourself is committed by seeing it.</p>
Committed against contractFrom QuickBooks, read this morning $486,200

Draft-and-confirm is for evidence that came from somebody else. What you saw yourself is committed by seeing it.

Option specified

.opt · 77 instances across 16 pages

A choosable block: a name, an annotation, and a state you can put it in.

Why it is a component

IT WAS FILED AS A ROW AND IT IS A SURFACE. This library's own kinds settle it — a row is 'a repeating line inside a surface', a surface 'holds other things, has a ground and a padding'. .opt has its own card ground, 24px of padding, an inset ring and a shadow, and 60 of its 60 instances are not inside a card at all — not one is, and the "30 of 35" this line hedged with was stale in both figures. It is the thing being chosen, drawn as its own object, and Q229 reads --r-card off that.

Anatomy

.nwhat the option is. 16px, medium.
.aoptionalone line saying what it means in practice. 40 of 60.

Variants

  • .sel chosen. Takes the accent fill and the selected ring. 14 uses. It was 13 and one page wrote on for it, which matches no rule and drew that option unchosen (21-jobs.html); Q229.

Rules

  • SELECTED IS A FILL, NOT A BORDER. The chosen option takes the accent as its ground so that a glance across a list finds it without reading.
  • The annotation is not quietened when selected, and that is measured rather than preferred: on dark the selected fill carries its ink at 4.97:1, so a 90% mix toward the fill lands at 4.31 and fails. There is no quiet step available, so hierarchy is carried by weight and size instead.
  • One line of annotation. An option needing a paragraph is a decision, and a decision gets a screen.

Not for

  • A row in a list of things you are reading rather than choosing. That is row.
  • A run of answers INSIDE a card. A raised, filled block within a card is a second card and the ladder does not stack, so selection there is the marker: that is pick_row.
Markup · from 07-one-question-at-a-time.html
  <div class="opt"><div class="opt-row"><span class="mark mark-lg on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"><path d="m5 12 5 5L20 6"/></svg></span><div class="body"><p class="n">Induction</p><p class="a">Faster than gas and easier to wipe. Needs pans that a magnet sticks to.</p></div></div></div>
  <div class="opt"><div class="opt-row"><span class="mark mark-lg"></span><div class="body"><p class="n">Gas</p></div></div></div>

Induction

Faster than gas and easier to wipe. Needs pans that a magnet sticks to.

Gas

row6

A repeating line inside a surface. Carries the shared slot vocabulary.

Row specified

.row · 562 instances across 43 pages

The deck's most-used component. A line inside a card: an optional marker, a name with a caption, and an optional trailing slot.

Anatomy

.icoptionala 32px marker box, with or without a state ground
.txwraps .n and .c so the trailing slot can be pushed right
.nthe name line
.coptionalone caption line, which may carry a provenance
.ctoptionala chip, a figure or a status
.gooptionala chevron, when the row opens something

Rules

  • Rows inside a card are separated by nothing. No dividers between rows.
  • The marker is vertically centered on the row, always, however many lines the name runs to.
  • A row has at most one trailing slot. A chip AND a figure is two ideas fighting.
  • The caption is where provenance goes — Their quote, 2 June, From QuickBooks, read this morning.
Markup · from 22-send.html
  <div class="row">
    <span class="ic"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span>
    <span class="tx"><span class="n">Tom Beckett: not yet, and here's why</span><span class="c">Alder Street · wants to know if rift oak can come in five weeks</span></span>
    <span class="ct"><span class="chip chip-status is-open">Open</span></span>
  </div>
  <div class="row">
    <span class="ic"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span>
    <span class="tx"><span class="n">Luis Reyes</span><span class="c">Tile · read this morning</span></span>
    <span class="go"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="color:var(--on-surface-variant)"><path d="m9 18 6-6-6-6"/></svg></span>
  </div>
Tom Beckett: not yet, and here's whyAlder Street · wants to know if rift oak can come in five weeks Open
Luis ReyesTile · read this morning

Inset row specified

.irow · 27 instances across 4 pages

A one-line row: a marker, a name, and a figure on the right. No caption, ever.

Why it is a component

It looks like a near-duplicate of row and it is not. THE CAPTION IS THE WHOLE DISTINCTION: ordinary rows may carry .c; inset rows never do. That is why an irow needs no .tx — the wrapper exists to group a name with its caption so the trailing slot can be pushed right, and with no caption there is nothing to group, so flex: 1 sits on .n itself.

Anatomy

.icoptionalan optional 32px marker
.nthe name, taking the width
.ctthe figure or count on the right

Rules

  • No caption. A line that needs a second line is a row, and the wrapper comes with it.
  • The trailing slot is not optional here. An irow with nothing on the right is a name in a box, and it should be a row or a plain line.
  • Rows of either kind are separated by the hairline --rule-ink and by nothing else.

Not for

  • Anything with two lines of text. That is row.
Markup · from 03-underneath-and-home.html
  <div class="irow">
    <span class="ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 9 5-9 5-9-5z"/><path d="m3 12 9 5 9-5"/><path d="m3 17 9 5 9-5"/></svg></span>
    <span class="n">Plans & drawings</span>
    <span class="ct t-num">9 sheets</span>
  </div>
Plans & drawings 9 sheets

Keep specified

.keep · 14 instances across 1 page

Something saved to a room's board: a thumbnail and what it is. No price, no obligation.

Why it is a component

It is the only row in the deck built around a picture rather than a name, and the thumbnail is what makes it one component rather than a row with an image in the marker slot: a 56px square is not a 32px .ic and does not line up with one.

Anatomy

.ththe 56px thumbnail, or a glyph standing in for one where there is no picture
.txwhat was saved, when, and which room it belongs to

Rules

  • No price and no call to action. A keep is something noticed, not something being decided; the moment it carries a figure it is an option and belongs in an intake.
  • The room is a chip on the keep, and no room is a real and expected state rather than an error. Ten of these exist and the unfiled one is the interesting case.
  • The thumbnail is always drawn, even when there is no image. A row of keeps with ragged left edges reads as broken.

Not for

  • A document. That is a row with a count on the right, or the viewer.
Markup · from 44-what-a-room-collects.html
  <div class="keep">
    <div class="th"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><path d="M4 5h16M4 10h16M4 15h10"/></svg></div>
    <div class="tx">
      <span class="t-sub" style="display:block; font-weight:500;">“Ask Dave whether the hall can take a wider door before the framing goes up.”</span>
      <span class="t-meta" style="display:block; margin-top:4px;">Saved by you · 9 May</span>
      <div class="chips" style="margin-top:9px;"><span class="unfiled">No room</span></div>
    </div>
  </div>
“Ask Dave whether the hall can take a wider door before the framing goes up.” Saved by you · 9 May
No room

Pick row specified

.pickrow · 38 instances across 7 pages

A choice among a card's own rows: a marker, a name, and one line saying what picking it would do.

Why it is a component

WHICH SHAPE IS A QUESTION ABOUT THE SURFACE. On the ground a choice is opt and selected is a fill; inside a card a fill cannot carry it, because a raised filled block within a card is a second card and the ladder does not stack. So the marker carries selection here — one surface, --rule-ink between rows, plain ink at every state. The class was in the vocabulary with no rule behind it until 2026-09-05: all thirteen rows inherited .mark from icons.css and nothing else, so the marker stacked above the name it marks.

Anatomy

.markthe 19px selection marker, SelectionMarker's md step. A ring in --rail-ink when empty, the accent fill and a tick when chosen. The ONLY element that changes between the states.
.nthe name. 16px, 400, going to 600 when chosen.
.aoptionalone line saying what picking it would do. 8 of 13.

Variants

  • .on chosen. Ticks the marker and takes the name to 600. 4 uses.

Rules

  • THE MARKER CARRIES SELECTION, and it is the only thing that changes. Weight, not hue, marks the chosen name.
  • The name never turns blue. Blue text is a link everywhere else in the product, so a blue chosen option reads as somewhere to navigate.
  • The rule sits BETWEEN rows — every row but the first — so a run does not open with a hairline under the card's own head.
  • Only real answers belong in the list. Leaving the question is an escape, and escapes sit under the primary action as a text action.

Not for

  • A choice standing on the ground, in a zone or bare. That is opt, and selected is a fill.
  • A row in a list of things you are reading rather than choosing. That is row.
Markup · from 48-delivery-reconciliation.html
  <div class="pickrow on"><span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"><path d="m5 12 5 5L20 7"/></svg></span><div><p class="n">Replace with the newer offer</p><p class="a">The source says inset doors.</p></div></div>
  <div class="pickrow"><span class="mark"></span><div><p class="n">Keep the earlier package answer</p><p class="a">Shaker fronts stay recorded.</p></div></div>
  <div class="pickrow"><span class="mark"></span><div><p class="n">Return this decision to questions</p></div></div>

Replace with the newer offer

The source says inset doors.

Keep the earlier package answer

Shaker fronts stay recorded.

Return this decision to questions

Answer row specified

.arow · 11 instances across 2 pages

A run where the answer, not the name, owns the right edge.

Why it is a component

A row's trailing slot is --text-xs in the variant ink — a QUALIFIER on the name, "Their quote, 2 June" — so putting an amount there demotes the one thing the reader came for. Here the answer sits at the name's own step, tabular, on one right edge, so a column of them compares vertically without the eye re-measuring each line. The rule is guidelines/data-hierarchy: every data card exists to answer one question, and a card that draws its inputs and its answer at the same weight makes the reader do the arithmetic the product was built to do for them.

Anatomy

.nwhat the row is about. 18px, regular.
.coptionalunder the NAME: who and when — "Reed Millwork · 14 May". A qualifier on the thing, not on the number.
.vthe answer. 18px, 600, tabular, on one right edge.
.coptionalunder the ANSWER: what it is measured against — "left of $22,000".

Variants

  • .clash the answer's ink when the row is over. On the value only, and on ONE row.

Rules

  • THE ANSWER OWNS THE RIGHT EDGE, at the name's own step. A figure in a row's trailing slot is a qualifier; a figure here is the finding.
  • WHAT THE ANSWER IS "OF" GOES UNDER THE ANSWER, not into the name's caption. "$22,000 set aside" beside "Primary bath" is a second fact about the room; "left of $22,000" under "$6,800" is that number doing its job.
  • One caption or the other, never both. Two captions on one line is the reader choosing which to read.
  • CLASH INK MARKS THE SINGLE ROW THAT IS OVER. Color on every row is five alarms, and a reader who sees five stops reading any of them.
  • Nothing repeats down the column. An identical glyph on every row carries no information and spends the width the amount needs.

Not for

  • A line you read or open, where the trailing thing is a note about the name rather than the answer. That is row.
  • ONE headline figure on a card. That is kv, at the display step.
  • A run of settled key-and-values that are not answers to one question. That is fact.
Markup · from 40-the-record-tabs.html
  <div class="arow"><span class="tx"><span class="n">Kitchen</span></span><span class="a"><span class="v clash">$3,400</span><span class="c">over $46,000</span></span></div>
  <div class="arow"><span class="tx"><span class="n">Primary bath</span></span><span class="a"><span class="v">$6,800</span><span class="c">left of $22,000</span></span></div>
  <div class="arow"><span class="tx"><span class="n">Mudroom</span></span><span class="a"><span class="v">$2,150</span><span class="c">left of $9,000</span></span></div>
Kitchen$3,400over $46,000
Primary bath$6,800left of $22,000
Mudroom$2,150left of $9,000

Group row specified

.dc-group · 0 instances across 0 pages

A named set selected whole, with independently disclosed members.

Why it is a component

Ported from the reviewed design-system JSX, props and prompt; decision controls is the first product consumer.

Rules

  • Selection stays on the neutral card rung; a marker and primary ring carry it.
  • Partial is a distinct mixed state.
  • Read-only removes the selection control and preserves disclosure.
  • Members indent to the name; the caller supplies ownership and counts.
Markup · from 50-decision-controls.html · GroupRow
<section class="dc-group is-selected"><button class="dc-grouphead" type="button" aria-pressed="mixed"><span class="dc-marker"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M5 12h14"/></svg></span><span class="dc-groupname"><span class="dc-name">Cabinets and storage</span><span class="dc-desc">Cabinet style, hardware and storage layout.</span></span></button><div class="dc-groupfoot"><span class="dc-tag">Mixed · 1 exception</span><button class="dc-textbtn" type="button" aria-expanded="false">4 decisions <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button></div></section>
Mixed · 1 exception

figure6

A number and what it means. Carries key and value rather than name and caption.

Fact specified

.fact · 149 instances across 21 pages

A key and its value, the key captioning the value from above. The smallest figure in the product.

Anatomy

.k
.v

Rules

  • Numerals are tabular. A column of figures that does not align is a column that cannot be read.
  • IT STACKS, AND IT NEVER PUTS A SENTENCE IN A COLUMN. Q225, ruled on this deck’s own content: 53 of the 144 values run past 25 characters and the longest is 119, while the longest key is 23 and the median is 9. A fixed key column is right for a Fact whose value is a figure and wrong for the third of them that are prose.
  • THE VALUE TAKES THE STEP ABOVE THE LABEL — the value at the lead step, the label at the one caption treatment the other four captions take.
  • THE HAIRLINE MARKS A SETTLED RUN, so the first item does not take one, and the block padding is symmetric because a Fact cannot see that it is last.
Markup · from 00-states.html
  <div class="fact"><span class="k">Set aside</span><span class="v t-num">$46,000</span></div>
Set aside$46,000

Key value specified

.kv · 13 instances across 9 pages

The one headline figure on a card: a caption, the answer at the display step, and its working in one line beneath.

Why it is a component

It is the other key-and-value in this library and it is drawn for the opposite job to fact. A fact holds a fixed label column so a RUN of them reads as a column; a kv pins both ends to the card's edges so ONE of them reads as a headline. hc.css says so in its own comment beside .fact: right-aligned values fight the edge as soon as either side is long, which is why a run is never drawn this way.

Anatomy

.k
.v
.woptionalthe working: the figures the answer was computed from, folded into ONE line — "$18,000 set aside · Reed Millwork quoted $21,400".

Variants

  • .clash the figure's ink when the answer is over. On the value only.

Rules

  • ONE OR TWO ON A SURFACE, NEVER A RUN. A run of key-and-values is a fact list. The two components look alike and are not interchangeable.
  • NO HAIRLINE BETWEEN THEM. fact carries .fact + .fact { border-top } and this deliberately does not, because it is not a settled run.
  • Numerals are tabular. 10-now.html puts t-num on the value.
  • THE KEY TAKES THE RAMP AS hc.css WRITES IT. tints.css used to escalate .kv .k to full ink on sage, lavender and clay, because the quiet ink measured 4.30:1 on light clay and failed AA. That sheet went with the tinted grounds on 2026-09-05; a card is white, where --on-surface-dense measures 8.38:1, so there is nothing left to escalate.
Markup · from 40-the-record-tabs.html
  <div class="kv"><span class="k">Kitchen cabinet package, over by</span><span class="v clash">$3,400</span><span class="w">$18,000 set aside · Reed Millwork quoted $21,400</span></div>
Kitchen cabinet package, over by$3,400$18,000 set aside · Reed Millwork quoted $21,400

Late pair specified

.late-pair · 8 instances across 8 pages

Two figures side by side: what settling by the date costs, and what settling after it costs.

Why it is a component

It is the deck's whole argument about deadlines made visible. A due date on its own is a source of anxiety a person can do nothing with; the same date beside $0 and $1,200 is a choice with a price on it. The free side is drawn first, always, so the reader meets the outcome that is still available before the one that is not.

Anatomy

.late-headhead optionalthe block's own title at the CardHead step, with an optional supporting line. Omitted only where the block already sits under a heading that names it — three hero frames do.
.late-figfreethe on-time figure: its label and its value, which is $0
.late-figcostedthe late figure: its label and its value
.late-whywhywhat the late figure is made of, quoted and assumed named separately

Rules

  • BOTH FIGURES OR NEITHER. The pair is a comparison; one side alone is a bill.
  • THE WORKING IS NOT OPTIONAL. 10-now.html states it directly: the slip is never shown without its cause. A number a person cannot interrogate is anxiety with a dollar sign on it, which is the thing this product exists to replace.
  • THE FREE SIDE IS DRAWN FIRST. Reading order is the argument: the outcome still available, then the one that is not.
  • Numerals are tabular, like every figure in this library.
  • IT NEEDS NO CARD AND NO RULE. It is a figure, not a record: two numbers and the working behind them are one thought, and a container divides it. Bare on whatever it sits on.
  • BARE MEANS IT CARRIES ITS OWN HEADING. With the card gone, naming the block and bounding it both fall to the block — and two large figures with no title are a number the reader has to reverse-engineer from its labels.
  • THE VALUE CARRIES THE COLOR, NOT THE LABEL. The label is the family's one caption treatment; colors-status lists a figure's VALUE among the five places a color belongs, and a caption naming a figure is not one of them.
  • ITS SPACING IS THE SHEET'S, NOT THE CALL SITE'S. 20px above the figures — the package's own figure, and the same gap whether what sits above is the head or a lead line inside a composite block — and the head's margin-bottom owns it where there is a head. Every one of the eleven instances typed its own margin-top until 2026-09-06: 16px once, 18px twice, 20px four times, --sp-block three times and nothing once, so on three pages the block sat flush against what was above it while its own figures sat 48px away.
Markup · from 32-the-deferral.html
  <div class="late-head"><span class="t">What waiting costs</span></div>
  <div class="late-pair">
    <div class="late-fig free">
      <span class="k">Settled by Thursday</span>
      <span class="v">$0</span>
    </div>
    <div class="late-fig costed">
      <span class="k">Settled after</span>
      <span class="v">$1,200</span>
    </div>
  </div>
  <p class="late-why"><b>Quoted:</b> the electrician's return call-out. <b>Assumed:</b> opening and closing the island wall a second time.</p>
What waiting costs
Settled by Thursday $0
Settled after $1,200

Quoted: the electrician's return call-out. Assumed: opening and closing the island wall a second time.

Figure trio specifiedpage-scoped

.fig3 · 3 instances across 1 page

A pair of figures side by side, each with its source underneath.

Why it is a component

It is the deck's answer to a number with no provenance: every figure in it carries where it came from, and the source line is not optional. From QuickBooks, read this morning is the component.

Anatomy

.fone figure: its key, its value, and the line saying where the value came from

Rules

  • THE SOURCE IS NOT OPTIONAL. A figure with no provenance is the thing this product exists to replace.
  • Two figures. Not one, which is a fact, and not four, which is a table.

No shared implementation

Every rule for .fig3 lives in 21-jobs.html's own <style> block. It is specified here and drawn once; a second page using it would redraw it. The gallery inlines those rules so the specimen is not naked.

Markup · from 21-jobs.html
  <div class="fig3">
    <div class="f">
      <span class="k">Contract</span>
      <span class="v">$792,000</span>
      <span class="s">One CO signed, one awaiting signature, one draft</span>
    </div>
    <div class="f">
      <span class="k">Committed</span>
      <span class="v">$486,200</span>
      <span class="s">From QuickBooks, read this morning</span>
    </div>
  </div>
Contract $792,000 One CO signed, one awaiting signature, one draft
Committed $486,200 From QuickBooks, read this morning

Segmented bar specified

.segbar · 9 instances across 3 pages

Quantities that sum to a fixed pool, shown as one bar. Two or three segments, and they always sum to the whole.

Why it is a component

Named .loanbar and scoped to the construction loan alone until 2026-08-10, deliberately, so the next thing tempted to wear a progress bar had to argue for it rather than reach for it. 10-now's contingency card made that argument: contingency is a fixed pool the same way a loan is, just self-set rather than bank-set, and generalizing the existing shape (rename, neutral tone names) was the honest move against building a second one that looked the same and drifted. Page 21's GMP cap and page 33's cost-plus target still carry no bar — a fixed pool is the test, and neither of those is one.

Anatomy

<i>segmentone per share of the pool — .strong, .mid, .faint, in descending certainty — each a width percentage. All present segments sum to 100%.
.segkeykeya swatch, the label and its figure on ONE line, one row per segment, with the figure owning the right edge. The bar is not readable without it.

Rules

  • ONLY A FIXED POOL, SET ONCE. The total must not move because of anything that happens against it — a loan (the bank sets it and stops) and a contingency reserve (set aside once, spending against it doesn't grow it) qualify. A contract sum does not: a change order moves the total itself, which makes a fill against it answer how far through am I instead of what is the rest of it doing.
  • THE SEGMENTS SUM TO THE WHOLE AND THE TRACK NEVER SHOWS. This is the test for whether anything could wear it: a fixed pool is fully allocated the day it is set, so there is no remainder to leave blank. A quantity with an unspoken-for gap is a progress fill wearing segments, and the gap is where a reader starts reading it as one.
  • IT IS A COMPOSITION, NOT A FILL. It answers what is the rest of it doing, never how far through am I — which is the question every other money surface in the product refuses.
  • THE RAMP IS BUILT ON --primary, NOT --primary-flat. The flat token cannot carry three steps: in dark it resolves to the same color as --primary, and --primary-deep is 1.26:1 from either. Mixing --primary toward --surface-card lets one declaration serve both themes, because the accent and the ground flip together.
  • EVERY FIGURE IS ALSO TEXT. The segments run 1.5:1 to 2.7:1 against each other, which is enough to separate them and not enough to carry meaning alone. The key states every amount at full contrast, and the bar is the glance rather than the source.
  • The figures sit under the bar, never inside it. A number on a fill is unreadable at both ends.
  • No animation. It is a state, not a process.
  • Tone names are neutral (.strong/.mid/.faint), not domain words — a caller supplies what strong means. Two tones are fine; not every fixed pool has three states.
Markup · from 13-money.html
  <div class="segbar">
    <i class="strong" style="width:62.5%"></i>
    <i class="mid"    style="width:22.5%"></i>
    <i class="faint"  style="width:15%"></i>
  </div>
  <div class="segkey">
    <div class="strong"><span class="k">Paid</span><span class="v t-num">$525,000</span></div>
    <div class="mid"><span class="k">Due</span><span class="v t-num">$189,000</span></div>
    <div class="faint"><span class="k">No date</span><span class="v t-num">$126,000</span></div>
  </div>
Paid$525,000
Due$189,000
No date$126,000

Comparison grid specifiedpage-scoped

.cmp · 1 instance across 1 page

Options compared line by line, with the differences marked and the agreements kept.

Why it is a component

One instance and it settles an argument the deck makes twice: you compare by line, not by quote. A column per candidate and a row per thing means the reader's eye moves along the thing they care about rather than down a document somebody else wrote.

Anatomy

.headsone column head per candidate
.linone line: the thing being compared, then a cell per candidate

Variants

  • .same the line where everything agrees. Stays on screen, quiet.

Rules

  • EMPHASIS MARKS A DIFFERENCE, NEVER A WINNER. The product does not choose.
  • The row where everything agrees stays on the screen and stays quiet. Removing it leaves the reader unable to tell whether the product looked and found them equal, or never looked.
  • Stack, never squeeze: the label takes its own line above the cells rather than becoming a column that shrinks the figures.

No shared implementation

Every rule for .cmp lives in 18-quote-compare.html's own <style> block. It is specified here and drawn once; a second page using it would redraw it. The gallery inlines those rules so the specimen is not naked.

Markup · from 18-quote-compare.html
  <div class="card cmp">
    <div class="heads">
      <span class="h">Reed<br>Millwork</span>
      <span class="h">Second<br>quote</span>
      <span class="h">Third<br>quote</span>
    </div>
    <div class="lin">
      <span class="lbl">Quoted</span>
      <div class="cells">
        <span class="cel differs">$21,400</span>
        <span class="cel differs">$23,900</span>
        <span class="cel differs">$25,150</span>
      </div>
    </div>
    <div class="lin">
      <span class="lbl">Crown molding</span>
      <div class="cells">
        <span class="cel absent">Not in it</span>
        <span class="cel">Included</span>
        <span class="cel">Included</span>
      </div>
    </div>
    <div class="lin">
      <span class="lbl">Drawer boxes</span>
      <div class="cells">
        <span class="cel">Particle board</span>
        <span class="cel">Particle board</span>
        <span class="cel differs">Plywood</span>
      </div>
    </div>
    <div class="lin same">
      <span class="lbl">Full-height ends · finish · hardware</span>
      <div class="cells">
        <span class="cel">Same</span>
        <span class="cel">Same</span>
        <span class="cel">Same</span>
      </div>
    </div>
  </div>
Reed
Millwork
Second
quote
Third
quote
Quoted
$21,400 $23,900 $25,150
Crown molding
Not in it Included Included
Drawer boxes
Particle board Particle board Plywood
Full-height ends · finish · hardware
Same Same Same

voice4

The product saying something. Graded on the volume ladder in states.css.

The product saying something specified

.say · 59 instances across 25 pages

Rung 2 on the volume ladder. It interrupts the content, because the reader cannot decide what to do next without it.

Anatomy

.tthe fact, as a sentence
.pwhy it matters
.actwhat to do now

Variants

  • .default neutral ground
  • .off offline. Says what still works, never what broke. No retry.
  • .clock A clock is running, and it says so — an eyebrow in --amber-deep, not a clay panel. NOT builder-only, which this entry claimed until it was counted: 9 instances across 8 pages, and 5 of those pages are the homeowner's. What a homeowner screen may not carry is a bare timestamp; the clay itself is fine for a consequence with a date on it, which is what the deletion screen on 15 and the subscription seat on 14 both use it for.
  • .who indigo. Somebody else got there first. News about a person, not a failure.
  • .gone the thing is not there. Always carries a way back.

Rules

  • The action slot is NOT optional. A say with no act is a bug in the page, not a variant.
  • It sits above the content and never over it. There is no scrim and no modal in this library.
  • There is no red rung. Amber means a clock, never an error.
Markup · from 14-two-of-us.html
  <div class="say">
    <span class="t">One decision reaches neither of you</span>
    <span class="p">Bench and hooks, in the mudroom. It matches nothing either of you holds, so nothing is nudging it along.</span>
    <button class="act" type="button">One of you take it</button>
  </div>
One decision reaches neither of you Bench and hooks, in the mudroom. It matches nothing either of you holds, so nothing is nudging it along.

Callout specified

.callout · 122 instances across 30 pages

Rung 1. A glyph and a short sentence, flat and quiet, in the reader's own words.

Anatomy

THE FIRST TWO SLOTS ARE ELEMENTS, NOT CLASSES, and this entry said otherwise until the gallery tried to draw it. It declared .gl and .p; the deck was then counted, and across 47 callouts the .p class appears ZERO times while .gl appears on 23. The CSS is the reason and it was right all along: hc.css styles .callout p, and icons.css styles body.hc-air .callout > svg with the same four declarations .gl carries — 17px, flex none, flex-start, and the same computed first-line offset. So inside a callout the class is redundant, which is why 24 of them omit it and look identical. The 23 that carry it are not wrong, only inconsistent.

<svg>glypha direct child. 17px, marking the paragraph's first line — sized and offset by icons.css, so it needs no class
<p>bodyone paragraph, styled by .callout p
.theading optionalpresent only when the coach is opening a thread
.cttrailing optionala count, on the line variant

Variants

  • .c-mute the default. An aside.
  • .c-idg an observation the product made
  • .c-amber a consequence worth knowing before you act
  • .c-coach the coach leaning in, opening a thread rather than reporting one
  • .line a row with a count pushed right. Three parts — glyph, label, count.
  • .c-sage DECLARED BY NOTHING. 6 instances carry it and no stylesheet on this sheet or in the design package has a rule for it, so it paints exactly what a bare callout paints. Either a tone somebody meant to add or a name that outlived its rule; recorded rather than silently deleted.

Rules

  • IT SITS BARE ON THE GROUND, AND THAT IS RUNG 0, NOT THE OUTLINE RUNG. No fill, no radius and no box padding: the outline rung is for a container-shaped absence or a set, and a one-sentence aside is neither. Boxed at the card radius it took the same footprint as the options above it and read as a fourth option the reader had failed to select. The scroll's own block gap is what separates an aside from what it is about.
  • A c-coach WITH a heading is a card and becomes a block: the glyph goes on its own line and the text takes the card's own left edge. Actions belong to the card, never to the glyph.
  • A callout with a trailing count is a line and stays a row. These two rules are exhaustive; anything else is the default.
  • The glyph marks the first line of the paragraph. It is not centered, because it is a mark on prose rather than a marker on a row.
Markup · from 09-first-run.html
  <div class="callout c-mute">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="flex-shrink:0; margin-top:1px;"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
    <p>Some of these answer themselves once you have answered others, so I won't know everything I need to ask until you start.</p>
  </div>

Some of these answer themselves once you have answered others, so I won't know everything I need to ask until you start.

Coach bubble specified

.coach-bubble · 28 instances across 7 pages

Something a person actually said, quoted back in their own words.

Why it is a component

17 instances, and it is the one component whose content is not the product's. Everything else on a screen is written by HouseChalk; this is a builder's sentence, kept.

Rules

  • IT IS A QUOTATION AND IS ALWAYS DRAWN AS ONE, with the marks. The moment it is paraphrased it becomes the product's voice and the component has no reason to exist.
  • It is never edited to fit. A long quote runs long.
  • It carries no action. What to do about what somebody said belongs to the surface around it.

Not for

  • The coach speaking. That is a callout at c-coach.
Markup · from 46-questions-to-ask.html
  <p class="coach-bubble">“Board goes up the week of the 2nd, so I need the cabinet color by the 28th at the latest. After that the painter is on a different house.”</p>

“Board goes up the week of the 2nd, so I need the cabinet color by the 28th at the latest. After that the painter is on a different house.”

Blocking state specified

.block · 3 instances across 3 pages

Rung 3. The only thing on the screen, and there are exactly three reasons for it.

Why it is a component

It is the loudest thing the library can do, so its whole specification is about when NOT to reach for it. states.css names the entire list and it is three long.

Anatomy

.twhat has happened, as a sentence
.pwhat it means. Two paragraphs in 2 of 3 instances.
.ctaact optionalthe way out, where there is one

Rules

  • THREE REASONS, AND THE LIST IS CLOSED: an invalid token, a client too old to read the data, and the first open of a lapsed account. Everything else that feels urgent is rung 2 with an action on it.
  • It replaces the screen rather than covering it. The phones that carry one hold the notch and the block and nothing else — no app bar, no scroll, no scrim.
  • z-index 45: over the app's own chrome, under the device.
  • It says what still works before it says what stopped. The binder is still here comes before what stops is adding to it.
Markup · from 15-account-and-being-told.html
  <div class="block">
    <div class="t">The binder is still here.</div>
    <div class="p">Everything about the house stays readable for as long as you want it: every decision, every color, every paper. Nothing gets deleted.</div>
    <div class="p">What stops is adding to it. Sam holds the subscription for this house and it ended on 30 June.</div>
    <a class="cta act">Talk to Sam about it</a>
    <a class="cta-ghost">Keep reading</a>
  </div>
The binder is still here.
Everything about the house stays readable for as long as you want it: every decision, every color, every paper. Nothing gets deleted.
What stops is adding to it. Sam holds the subscription for this house and it ended on 30 June.
Talk to Sam about it Keep reading

state10

What a surface shows when it has no content, stale content, or content in flight.

As-of line specified

.asof · 29 instances across 12 pages

One grammar, three sources: when this was read, and from where.

Rules

  • It is a fact about the data, not a status of the screen.
  • Never a spinner and never a refresh button.
Markup · from 00-states.html
  <div class="asof act">As of Tuesday evening. If Dave has repriced this since, you won't see it here until you are back online.</div>
As of Tuesday evening. If Dave has repriced this since, you won't see it here until you are back online.

Skeleton specified

.skel · 11 instances across 1 page

The shape of what is coming, at the size it will be.

Rules

  • Matches the real layout. A skeleton that does not is a second layout to maintain.
  • No shimmer. It is a placeholder, not an animation.
Markup · from 00-states.html
  <div class="skel-card">
    <div class="skel line"></div>
    <div class="skel line mid"></div>
    <div class="skel line short"></div>
  </div>

Inert capability specified

.inert · 15 instances across 5 pages

Rung 1. A card you cannot act on, with the reason available rather than asserted.

Why it is a component

It is the deck's answer to the disabled control, and the answer is that a disabled thing has to say why. 5 instances, and 3 of them are .reached — the state where the reader has asked and the reason is showing. OWED: 3 of the 6 .ctl carry no why at all (00-states:663, 07:355, 15:881), which is the component's one required part missing. They draw the pill and stay non-interactive until the sentence is written.

Anatomy

.whythe reason. Hidden until .reached.
.ctloptionalthe control that is not available, drawn as an outline pill rather than a grayed or filled button. It carries the ask: asking is the interaction, so where a why exists this is a <button> with aria-expanded and aria-controls.

Variants

  • .reached the reader asked, so the reason is showing. 3 of 5.

Rules

  • THE REASON IS ALWAYS PRESENT IN THE MARKUP AND HIDDEN UNTIL ASKED FOR. A disabled control with no explanation is the thing this component exists to replace, and hiding the reason behind a tap is different from not having one.
  • It is a card, not a control. Graying out a button says no; this says who can and what you can do instead.
  • The reason names a person where there is one: Dave is the only person who can void a change order beats insufficient permissions.
Markup · from 22-send.html
  <div class="card inert">
    <div class="t-lead" style="color:var(--on-surface-variant)">Void CO-1</div>
    <div class="why" style="display:block">Dave is the only person who can void a change order. You can draft one, send it, and chase the signature.</div>
  </div>
Void CO-1
Dave is the only person who can void a change order. You can draft one, send it, and chase the signature.

In-flight mark specified

.inflight · 11 instances across 5 pages

Rung 0. A 9px hollow ring on the end of a line, meaning this has not left the phone yet.

Why it is a component

It is the whole of the deck's offline story and it is nine pixels. Rung 0 says the data renders its own state and nothing is added — so a queued item is not a banner, a toast or a retry button; it is the line it belongs to, wearing a mark.

Rules

  • INSIDE THE LINE, NEVER BESIDE IT. Six of the ten sit in a .n and four in a .t-meta. A trailing slot would make it a status the row reports rather than a mark on the sentence, and the row already has a status to report.
  • Hollow, not filled. A filled dot in this deck means a state that has settled; this one has not.
  • No count, no percentage, no retry. The queue is not a thing the reader manages.
  • It disappears on its own. Nothing replaces it and nothing confirms it — the absence of the ring is the confirmation.

Not for

  • A failure. Nothing in this library says a send failed, because the product retries and the reader cannot help.
Markup · from 26-offline-and-capture.html
  <div class="row">
    <span class="tx"><span class="n">Rough-in walked with Tony Alvarez<i class="inflight"></i></span><span class="c">8:10am · filed the moment you ticked it</span></span>
  </div>
Rough-in walked with Tony Alvarez8:10am · filed the moment you ticked it

Year-two empty specified

.empty-y2 · 9 instances across 6 pages

Rung 1. Nothing is outstanding, said as good news on a settled ground.

Why it is a component

An empty list is the commonest state in this product and the easiest one to draw as a failure. This is the deck's rule that a finished list is an achievement: it takes the settled mark, not the neutral one, and it says what happened rather than what is missing.

Anatomy

.sthe fact, as a sentence. Nothing is outstanding.
.cwhy it is empty and what would put something here

Rules

  • SETTLED GROUND, NOT NEUTRAL. This is the state where the reader is finished, and a gray box says the screen is broken.
  • It says what happened, with a date where there is one. The last item on your list was closed on 4 October is the sentence; No items is not.
  • No illustration and no call to action. There is nothing to do, which is the message.

Not for

  • A list that is empty because it has not started yet. That is dated, and it is amber.
Markup · from 16-the-last-mile.html
  <div class="empty-y2">
    <span class="s">Nothing is outstanding.</span>
    <span class="c">The last item on your list was closed on 4 October. Anything new goes here.</span>
  </div>
Nothing is outstanding. The last item on your list was closed on 4 October. Anything new goes here.

Dated container specified

.dated · 6 instances across 3 pages

Rung 1. Not empty — not yet. An amber ground and the date it opens.

Why it is a component

The distinction from empty_y2 is the whole reason both exist, and it is a distinction about time rather than about content. One is finished and one has not begun, and drawing them the same way tells the reader nothing.

Anatomy

.dwhen it opens. Opens the week of 12 July.
.swhat will fill it, and that there is nothing to do meanwhile

Rules

  • AMBER MEANS A CLOCK, NOT A WARNING. states.css is explicit that there is no red rung and that heat never rates a state; this ground says time is involved.
  • It always carries a date or a trigger. A not yet with no when is worse than an empty box.
  • It ends by saying there is nothing for the reader to do. That sentence is the point of the component.

Not for

  • A deadline the reader has to meet. A clock running against somebody is rung 2 and says what to do.
Markup · from 00-states.html
  <div class="dated">
    <span class="d">Opens the week of 12 July</span>
    <span class="s">Your list gets made on the final walk. Until then there's nothing to put in here. And nothing you need to do about it.</span>
  </div>
Opens the week of 12 July Your list gets made on the final walk. Until then there's nothing to put in here. And nothing you need to do about it.

Cold start specified

.cold · 2 instances across 1 page

Rung 0, escalating to 2. The launch surface, holding the product's identity until there is something to replace it with.

Why it is a component

It is the one screen where the shell is always there is false, so the no-spinner rule cannot simply be inherited and has to be restated. Two instances, and the second is the one that matters: the abnormal wait.

Anatomy

.logoidentitythe mark and the wordmark, on the hero gradient
.lateoptionalthe abnormal wait. Rung 2, and the only sentence this screen ever carries.

Rules

  • NO SPINNER, NO PROGRESS BAR, NO MESSAGE while the wait is normal. The product holds its own identity; it does not narrate.
  • The late sentence is in the reader's terms — what is happening to them, never what the system is retrying. We are having trouble reaching it rather than a retry count.
  • It takes the hero gradient and --on-hero, and that ink does not flip with the theme because this surface does not either.
  • z-index 45: above the dock and the scrim, below the notch. A blocking surface covers the app's chrome, which is the point; it does not cover the device.
Markup · from 00-states.html
  <div class="cold">
    <div class="logo logo-mark on-hero"></div>
    <div class="logo logo-wordmark"></div>
    <div class="late">This is taking longer than it should. Nothing is wrong with your house. We are having trouble reaching it.</div>
  </div>
This is taking longer than it should. Nothing is wrong with your house. We are having trouble reaching it.

Lapsed banner specified

.lapsed · 3 instances across 3 pages

Rung 1. One line: the account stopped, everything stays.

Why it is a component

It is the smallest component in the library and it carries the product's most load-bearing promise. Two instances, one line each, and no action — because there is nothing the reader can do from here and pretending otherwise would be the whole failure mode.

Rules

  • ONE LINE, AND IT SAYS WHAT STAYS. Read-only since 30 June. Everything here stays. The date is not optional; a lapse with no date reads as a threat.
  • No action on it. The person who can restart the subscription may not be the person reading, and the block state is where that conversation happens.
  • It is rung 1 and it stays rung 1 forever. Only the FIRST open of a lapsed account is allowed to block.

Not for

  • The first open after lapsing. That is block, rung 3, and it happens once.
Markup · from 25-first-run-and-setup.html
  <div class="lapsed">Read-only since 30 June. Everything here stays.</div>
Read-only since 30 June. Everything here stays.

field5

Collects something from a person.

Field specified

.field · 132 instances across 26 pages

Anatomy

.lbwhat this is
.inthe input. .on when filled.
.phoptionalan example of the value, while the input is empty
.hintoptionalwhy we are asking, or what happens next

Rules

  • The hint explains the ask, never the format. Ten characters minimum is a format; You are joining a company that already exists is an ask.
Markup · from 22-send.html
  <div class="field">
    <span class="lb">Your message</span>
    <div class="in on" style="min-height:76px">Tom, need the cabinet pick by the 29th or the shop pushes us a week. Three that fit, with prices.</div>
    <span class="hint">He gets this as a text with one link. No app, no password.</span>
  </div>
Your message
Tom, need the cabinet pick by the 29th or the shop pushes us a week. Three that fit, with prices.
He gets this as a text with one link. No app, no password.

Toggle row specified

.tg · 12 instances across 4 pages

A switch with a name and a consequence under it.

Why it is a component

Nine instances and every one of them carries a caption, which is the specification: this product does not ship a switch whose effect the reader has to guess.

Anatomy

.txthe name and what turning it off actually does
.swthe switch

Variants

  • .lock on and not changeable, because a floor applies. 1 of 9. Quiet, not crossed out: --primary-flat, never a disabled gray.

Rules

  • THE CAPTION SAYS WHAT HAPPENS, NOT WHAT THE SETTING IS. Off means they hear on Friday like everyone else is a caption; Enable notifications is a label repeated.
  • A locked toggle stays visible and stays on. Hiding a floor is how a reader discovers it by being surprised.
  • 9 of 9 carry a caption. A toggle without one has not been thought through.
  • TOGGLES IN A RUN ARE SEPARATED BY A HAIRLINE, and the first does not take one. --rule-ink separates two rows and is meant to be felt rather than seen — the design package's own definition of the weight, as against --outline for a card's edge and --rail-ink for structure. The 'no dividers between sibling rows' rule is about the heavier line, and the argument that removed this hairline was reading it out of a BRAND.md the package names as superseded.
  • The switch centers against the whole block, not against the name's first line.
  • A locked switch keeps --primary-flat and never a neutral disabled gray: at #B4B4B4 the track is 2.07:1 on a card and the white thumb is 2.07:1 on the track, both under the 3:1 non-text bar at once, so a reader cannot tell which way it is set. --primary-flat is 4.74:1 on both, and says the true thing — this is ON and you may not move it.
Markup · from 25-first-run-and-setup.html
  <div class="tg">
    <span class="tx"><span class="n">Weekly, Friday evening</span><span class="c">The floor for a cost plus job. You can send it more often; you can't send it less.</span></span>
    <span class="sw on"></span>
  </div>
  <div class="tg">
    <span class="tx"><span class="n">The day something changes</span><span class="c">Off means they hear on Friday like everyone else.</span></span>
    <span class="sw"></span>
  </div>
Weekly, Friday eveningThe floor for a cost plus job. You can send it more often; you can't send it less.
The day something changesOff means they hear on Friday like everyone else.

One-time code specified

.otp · 2 instances across 2 pages

Six boxes for a six-digit code, with the next one waiting.

Why it is a component

Two instances on two pages, and it is the only input in the deck that is not a field. Its rationale was rewritten when the second one landed: the first draft said it was here because "the front door is the one surface where the reader is doing something mechanical rather than deciding", which read the component off the only page that had it. The real rule is in _ds/forms.css and it is about consequence, not surface: this is a CONSEQUENCE GATE and never an entry gate. It appears where money moves and the session cannot vouch for who is moving it, which is one frame on the front door and one on a link.

Variants

  • .next the box the next digit goes into. Exactly one at a time.
  • .on a box that has a digit in it: the accent edge plus a 3px glow at 18%. Defined in _ds/forms.css since the component landed and listed here for the first time under Q236 — the register carried next alone, so the state that says how far you have got was undocumented on every surface that reads this file.

Rules

  • Six boxes, always drawn, always the same size. A code entry that grows as you type moves the target.
  • One box is marked as next and it is the only affordance. There is no cursor and no keyboard hint.
  • No countdown on it. A code that is expiring says so in a line underneath, at rung 1, because a clock ticking on the box is anxiety with no action attached.
  • IT NEVER STANDS BETWEEN A PERSON AND THEIR OWN HOUSE. A page putting this in front of a login has misread the model. What it gates is an amendment, and the amendment is always drawn on the same screen as the boxes.
Markup · from 01-the-front-door.html
  <div class="otp">
    <i>4</i><i>6</i><i>9</i><i class="next"></i><i></i><i></i>
  </div>
469

Filter bar specified

.dc-filterbar · 0 instances across 0 pages

One selected filter narrows a view without becoming its primary action.

Why it is a component

Ported from the reviewed design-system JSX, props and prompt; decision controls is the first product consumer.

Rules

  • Exactly one option is selected.
  • Selection uses primary ink and a 1.5px ring, never a fill.
  • Wrap options and preserve 44px touch targets.
Markup · from 50-decision-controls.html · FilterBar
<div class="dc-filterbar" role="group" aria-label="Who chooses"><button type="button" aria-pressed="true">All</button><button type="button" aria-pressed="false">Homeowner</button><button type="button" aria-pressed="false">Builder</button></div>

marker4

A small fixed-size thing beside content. Never a label.

Chip specified

.chip · 149 instances across 27 pages

A small pill carrying a count, a status, a money figure or a date.

Variants

  • .chip-count a number. 62 uses.
  • .chip-status with is-settled (14), is-open (19) or is-waiting (2), and bare once — 47-proof-behind-a-decision.html:339 draws .chip chip-status with no modifier and takes is-open's neutral ink.
  • .chip-money a figure. 17 uses.
  • .chip-time a date or a deadline, as a fact and never a countdown. 35 uses.
  • .chip-code a product code or identifier, monospaced and tracked out — the one chip somebody reads a character at a time. 26 uses, the third most drawn of the set.
  • .chip-person who. 3 uses.
  • .chip-conflict over, clashing, nobody has this. Terracotta, and reserved for a genuine clash — a price above an allowance is chip-money, not this. 2 uses.

Rules

  • A chip may carry an outline; a card may not.
  • A chip never carries an action.
  • SEMIBOLD, BECAUSE A CHIP IS NOT PROSE. 12px at 600, line-height 1, in a 4px-by-8px pill. Q230: the weight went to the design package, the padding to the deck, and the app was drawing the pill at exactly double on both axes.
  • A chip may carry a leading glyph and no page does. .chip's gap: 4px has nothing to space in any of the 181 real instances, and HcChip has no slot for one.
Markup · from 45-the-handoff.html
  <div class="chips">
    <span class="chip chip-count">4 colors</span>
    <span class="chip chip-status is-settled">Locked</span>
    <span class="chip chip-status is-open">Open</span>
    <span class="chip chip-money t-num">$21,400</span>
    <span class="chip chip-time">Closes Thursday</span>
  </div>
4 colors Locked Open $21,400 Closes Thursday

Marker box specified

.ic · 164 instances across 20 pages

A 32px box holding one 16px glyph, at the head of a row.

Why it is a component

99 instances and the deck's only icon container. It is also where a rule that is stated in the CSS has never been stated in the library: the box gets a background only when the background is carrying a state.

Anatomy

<svg>glyphone, 17px, centered in the box. 85 of 99; the other 14 hold a .tick instead — see the rules.

Variants

  • .done a settled ground. DEFINED AND DRAWN ZERO TIMES.
  • .waiting a waiting ground. DEFINED AND DRAWN ZERO TIMES. It was called warn until Q222: this note already said "waiting", the stylesheet comment above it already said "the ink carries the clock", and the app's one real call site already drew a clock — only the class name said otherwise. warn is the word the design package struck from Option for the same reason, and a ground here means a state rather than a judgment.
  • .ic-first lifts the box onto the first line of prose beside it. One use.

Rules

  • The box is 32px whether or not a background appears, so a row with a state marker and a row without line up.
  • A GROUND MEANS A STATE AND NOTHING ELSE. icons.css says it outright — 'if the color is not carrying a state, the glyph is bare' — and there are exactly two states it may carry, settled and waiting.
  • It is centered on the row, however many lines the name runs to. .ic-first is the exception and it is for prose, where the glyph marks the first line rather than the block.
  • THE TWO STATE GROUNDS ARE UNUSED AND ONE PAGE REINVENTED THEM. .ic.done and .ic.waiting are defined and drawn zero times, while 03-underneath-and-home puts a .tick inside the box on 14 rows to say settled, open or waiting — a nested dot doing the job the ground was built for. Two mechanisms for one idea, and the unused one is the library's. Worth settling before either is built on.
  • THE GLYPH IS 16, NOT 17. The prose in icons.css said 17 while all three declarations under it said 16; the design package draws 17 and 16 is the rung, which is how a deck measurement enters the library. The app was drawing 24 — the wrong constant entirely, under a comment claiming 16.

Not for

  • A marker you can act on. That is mark, which is round and 19px and belongs to a choice.
Markup · from 14-two-of-us.html
  <div class="row">
    <span class="ic"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span>
    <span class="tx"><span class="n">Neutral</span><span class="c">No state ground</span></span>
  </div>
  <div class="row">
    <span class="ic done"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="m5 12 5 5L20 6"/></svg></span>
    <span class="tx"><span class="n">Settled</span><span class="c">The ground carries the state, not the row</span></span>
  </div>
NeutralNo state ground
SettledThe ground carries the state, not the row

Selection marker specified

.mark · 64 instances across 11 pages

A round 19px selection marker, ticked or empty, belonging to a choice.

Why it is a component

It is the one marker a person acts on, and it was drawn at 17, 18 and 18px on its three sites with a hand-nudge each before icons.css settled it. Its size is derived rather than picked: 19px against a 15px label is the 1.27 ratio that makes a marker read as a control instead of a bullet.

Anatomy

<svg>tick optional11px, present only when the marker is on. An empty marker is the unchosen state and draws nothing.

Variants

  • .mark-lg 24px with a 14px tick, for a 19px title. Holds the same ratio at larger type. 5 uses.
  • .on chosen. Takes the accent ground.
  • .mark-glyph an svg whose own artwork carries the ring, so it needs no ground. Same 19px footprint. 2 uses.

Rules

  • ALWAYS CENTERED on the row it marks. It belongs to the whole option rather than to its first line, which is the opposite of a glyph on prose.
  • It is sized by the type it marks, not by preference: 19px against a 15px label, 24px against a 19px one.
  • An empty marker is a real state and draws nothing but its ground. There is no third state.

Not for

  • Labeling a row. A marker that cannot be chosen is an ic.
Markup · from 07-one-question-at-a-time.html
  <div class="opt sel"><div class="opt-row"><span class="mark mark-lg"></span><div class="body"><p class="n">Coffee setup</p></div></div></div>
  <div class="opt"><div class="opt-row"><span class="mark mark-lg"></span><div class="body"><p class="n">Stand mixer</p></div></div></div>

Coffee setup

Stand mixer

Pill specified

.pill · 50 instances across 6 pages

A soft round-ended label, 56 uses. What somebody said, or a thing they can say back.

Why it is a component

It is the deck's only marker that is neither a status nor a count, and the distinction is load-bearing: a chip reports, a pill answers. 15 of the 56 are ticked, which is the whole reason it is not a chip.

Variants

  • .pill-on chosen. Success ground. 15 uses.
  • .pill-idg indigo. DEFINED AND DRAWN ZERO TIMES.

Rules

  • A pill can be chosen; a chip cannot. That is the whole difference and it decides which to reach for.
  • It carries a phrase, not a figure. A number in a pill is a chip wearing the wrong shape.
  • No outline. The ground is the separation, the same rule a tinted card follows.

Not for

  • A status. chip-status says settled or open, and it is never tappable.
Markup · from 02-the-space-conversation.html
  <span class="pill pill-on">Yes, exactly</span>
  <span class="pill">Not quite</span>
Yes, exactly Not quite

action3

A thing to do now.

Primary action specified

.cta · 244 instances across 42 pages

One per screen. Full width, the role accent, and the thing the screen is for.

Variants

  • .cta-ghost the quiet way out. The primary's geometry, transparent ground, accent ink, centered.
  • .cta-outline a committing action that must remain visibly a button without taking the filled primary weight. Full-width, transparent, with the role-accent outline.

Rules

  • One primary per screen. A second one is two screens.
  • A destructive action never takes the filled primary weight. After its consequence is stated, it takes the outline.
  • Ghost is for a quiet way out, not for a state-changing commit.
  • The ghost is centered. Hung off the left margin it reads as a stray link.
Markup · from 31-the-four-types.html
  <a class="cta">Tell Sam what you think</a>
  <a class="cta-outline">Remove this seat</a>
  <a class="cta-ghost">Not now</a>

Icon button specified

.icon-btn · 341 instances across 43 pages

A round 38px button in an app bar slot, trailing unless it is the back chevron, carrying one glyph.

Why it is a component

148 instances and the most uniform thing in the deck: every single one holds exactly one svg and nothing else. A component that is already unanimous is cheap to specify and expensive to leave unspecified, because the first exception will look like a local decision.

Anatomy

<svg>glyphone, 17px. 148 of 148 — no instance carries a label, a count or a second glyph.

Rules

  • Exactly one glyph. Never a label beside it, never a badge on it.
  • It lives in an app bar's trailing slot, with one exception: the back chevron takes the leading slot on the 19 bars where nothing else holds it. A round button elsewhere on a screen is a cta or a row's affordance, not this.

Not for

  • A count. A number beside an icon is a chip, and it goes in a row's trailing slot.
Markup · from 10-now.html
  <div class="icon-btn"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg></div>

media4

A picture, a drawing, or a document.

Dated spine specified

.spine · 4 instances across 3 pages

A vertical timeline of dated nodes, each one settled, current or not yet.

Why it is a component

It is the only component in the deck that draws time as a line, and the rail down its left is the one place a line in this product is allowed to be structure rather than a whisper.

Anatomy

.spone node: its marker and rail, then a date, a title and a supporting line

Rules

  • THE RAIL IS STRUCTURE AND CARRIES MORE WEIGHT THAN A DIVIDER. It is the line the eye follows down the timeline, at --rail-ink rather than the --rule-ink used between rows.
  • The node that matters is the one that has not happened. Settled nodes above it collapse, because four of them push the next one off the screen.
  • Every node carries a date or the thing it depends on. Releases when the final inspection passes on 24 June, not on a date of its own is a node; Next alone is not.
Markup · from 41-the-time-axis.html
  <div class="spine">
    <div class="sp">
      <span class="n"><i class="done"></i><b></b></span>
      <span class="bd">
        <span class="d t-num">24 Mar 2026 - 11 Feb 2027 · paid</span>
        <span class="t">Draws 1 to 4</span>
        <span class="s"><span class="t-num">$525,000</span>. Permit and site work, foundation, framing and roof dried in, rough-in inspections passed.</span>
      </span>
    </div>
    <div class="sp">
      <span class="n"><i class="now"></i><b></b></span>
      <span class="bd">
        <span class="d">Next · on the June inspection</span>
        <span class="t">5 · Drywall and interior finishes</span>
        <span class="s"><span class="t-num">$189,000</span>. Releases when the final inspection passes on 24 June, not on a date of its own.</span>
      </span>
    </div>
    <div class="sp">
      <span class="n"><i></i><b></b></span>
      <span class="bd">
        <span class="d">Last</span>
        <span class="t">6 · Certificate of occupancy</span>
        <span class="s"><span class="t-num">$126,000</span></span>
      </span>
    </div>
  </div>
24 Mar 2026 - 11 Feb 2027 · paid Draws 1 to 4 $525,000. Permit and site work, foundation, framing and roof dried in, rough-in inspections passed.
Next · on the June inspection 5 · Drywall and interior finishes $189,000. Releases when the final inspection passes on 24 June, not on a date of its own.
Last 6 · Certificate of occupancy $126,000

Room card specified

.room · 20 instances across 3 pages

A room on the house's board: a picture if there is one, a name always.

Why it is a component

29 instances and the shape a homeowner navigates by. It also carries the deck's clearest naming trap, below.

Anatomy

.artoptionalthe photograph, where the room has one
.pthe plate: the room's name and what it has settled

Variants

  • .empty no picture yet. 13 of 29 — nearly half, and the ordinary case on day one.
  • .full spans the whole grid row. 2 uses.

Rules

  • empty AND full ARE NOT OPPOSITES AND THE NAMES SUGGEST THEY ARE. empty means the room has no picture; full means the card spans the grid — grid-column: 1 / -1. room empty full is a real and correct combination: a room with no picture, drawn wide. Nothing else in the library has two modifiers that read as a contradiction.
  • A room without a picture is drawn at the same size as one with. Half the board is empty on day one and a ragged grid says the product is broken rather than new.
  • The name is always present. The picture never replaces it.

Not for

  • A decision inside a room. Rooms are the way in; decisions are rows.
Markup · from 03-underneath-and-home.html
  <div class="room">
    <img src="_ds/img/kitchen.webp" alt="Kitchen" />
    <div class="p"><div class="n">Kitchen</div><div class="c">5 settled · 7 open</div></div>
  </div>
Kitchen
Kitchen
5 settled · 7 open

Document viewer specifiedpage-scoped

.viewer · 1 instance across 1 page

A document on screen: a sheet on a dark well.

Why it is a component

One instance, and it is specified because the next document surface will be built from it. It is the only place in the deck where the product shows something it did not draw.

Anatomy

.sheetthe page itself, at its own aspect ratio, on a ground darker than the app's

Rules

  • The well is darker than the page ground, so the sheet reads as a physical thing on a surface rather than as part of the app.
  • The sheet keeps its own aspect ratio. A document cropped to fit is a document the reader cannot trust.
  • No annotation layer, no markup tools. This shows a paper; it does not edit one.

No shared implementation

Every rule for .viewer lives in 40-the-record-tabs.html's own <style> block. It is specified here and drawn once; a second page using it would redraw it. The gallery inlines those rules so the specimen is not naked.

Markup · from 40-the-record-tabs.html
  <div class="viewer">
    <div class="sheet"></div>
  </div>