GitHub

Component promises

Read at build time. Every number below comes from scanning component-tokens.css and the component sources when this site was built — a snapshot of that checkout, not a live read.

Tier 3 is a promise: every token in component-tokens.css tells a spoke “override this and the component moves.” This page checks that promise against delivery — what each component’s source actually reads.

The other two views each see one half of this. The token graph’s orphan hooks check knows a token is unread but reports a flat list of names with no owner, so six tokens describing one component’s missing user block read as six unrelated findings. The Theming surface table on every component page is built from what the component reads — by construction it can only show hooks that are delivered, so an over-promised token is invisible on the exact page where the promise is published.

123tier-3 declared
99read (80%)
24gaps, across 1 of 33 namespaces
0exempt by decision
0name a component (0 open)
0ad-hoc (under-promised)
0undefined reads

Why the split matters

“Unread” is one word for three different defects with three different fixes, and only one of them is a wiring job. The kind is derived, not authored: missing-part falls out of “are ALL of this part’s tokens unread”, which is the same evidence a human uses. A part of one token can never be called missing — there are no siblings to compare against, so it stays unwired and a person decides.

no component 24
The namespace names no component. Either it is staged ahead of code that has not landed (legitimate, if documented in SPEC.md) or it is a ported surface superseded by a component under a different name.
missing part 0
Every token naming this part is unread, so the part has no markup behind it. Closing this is a FEATURE — build the part or delete the tokens. Wiring is not available as an option.
unwired 0
Sibling tokens naming the same part are read, so the markup exists and the component simply does not consult this hook. A CSS edit, and the only kind here that is purely a wiring fix.
exempt 0
Unread by DECISION, not by accident — the component exists and these map onto real elements in it. Held exempt from the wire-or-delete rule; see the exemptions table for the owner and for what a spoke gives up. Not counted as a gap, still listed by name.

Over-promise — declared, not read

--grid-* no component 0/24

no component row 0/6 of this part read

TokenResolves to
--grid-row-bg#fcfcfc
--grid-row-bg-hover#f0f0f0
--grid-row-bg-selectedcolor-mix(in srgb, #46a758 6%, transparent)
--grid-row-border-color#d9d9d9
--grid-row-font-sizeclamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem)
--grid-row-stripe-bg#fcfcfc

no component header 0/5 of this part read

TokenResolves to
--grid-header-bg#fcfcfc
--grid-header-border-color#cecece
--grid-header-color#202020
--grid-header-font-sizeclamp(0.625rem, 0.56rem + 0.32vw, 0.75rem)
--grid-header-weight550

no component toolbar 0/3 of this part read

TokenResolves to
--grid-toolbar-bg#fcfcfc
--grid-toolbar-gap0.5rem
--grid-toolbar-padding0.5rem 0.75rem

no component cell 0/2 of this part read

TokenResolves to
--grid-cell-padding-x0.75rem
--grid-cell-padding-y0.5rem

no component font 0/2 of this part read

TokenResolves to
--grid-font-family'DM Sans', sans-serif
--grid-font-sizeclamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem)

no component pagination 0/2 of this part read

TokenResolves to
--grid-pagination-bg#fcfcfc
--grid-pagination-border-color#cecece

no component bg 0/1 of this part read

TokenResolves to
--grid-bg#fcfcfc

no component border 0/1 of this part read

TokenResolves to
--grid-border-color#cecece

no component radius 0/1 of this part read

TokenResolves to
--grid-radius0.5rem

no component shadow 0/1 of this part read

TokenResolves to
--grid-shadow0
Fully delivered 32 namespaces

Every declared token in these is read. Listed so the page shows the whole surface, not only its failures.

  • --alert-box-* 1
  • --app-shell-* 1
  • --avatar-* 9
  • --back-to-top-* 1
  • --badge-* 3
  • --breadcrumbs-* 3
  • --button-* 6
  • --card-* 3
  • --command-palette-* 2
  • --confirm-dialog-* 1
  • --dialog-* 4
  • --dropdown-menu-* 2
  • --empty-state-* 4
  • --entity-search-* 2
  • --file-list-* 2
  • --filter-container-* 1
  • --filter-dropdown-* 1
  • --focus-ring-* 3
  • --form-* 15
  • --header-nav-* 1
  • --link-column-* 1
  • --loading-spinner-* 2
  • --nav-dropdown-* 1
  • --popover-* 1
  • --progress-bar-* 4
  • --search-panel-* 1
  • --side-dialog-* 5
  • --sidenav-* 10
  • --snackbar-container-* 1
  • --stat-* 3
  • --tab-layout-* 4
  • --tooltip-* 1

Tokens naming another component

A tier-3 token is for a part the component draws itself. When the “part” is really another component, it should be composed and themed through its tokens. --sidenav-user-avatar-bg was the first of these found — esa-avatar already owns --avatar-bg, so the token was a second avatar implementation living in the token file. The tell is in the name: two nested part words means you have stopped naming a part and started naming a component tree.

Reported apart from the gap tally, and deliberately so — this is a different defect class, and folding classes into one bucket is what let --topbar-* hide. A group appears only when at least one of its tokens has a direct counterpart in the child’s namespace; that filter is what removes the noise. The 13 tokens naming icon are suppressed because esa-icon has no tier-3 surface at all, so a parent sizing a glyph inside its own layout is correct. --pagination-button-color is suppressed because esa-button declares only --button-on-warning — there is no --button-color to duplicate.

Read by more than one component

A tier-3 token themes one component, so several readers is worth a look — but it is not one defect, it is three different facts, and only two of them are problems. Sorted by reader count, so the widest blast radius is first.

The component doc pages read the same data: each one splits its theming surface into the hooks wired to it and the shared tokens it merely consumes, so a spoke author can tell “mine to turn” from “turning this moves the family”. This table is the system-wide view of the second half. Everything here is derived — a new component that reads --dialog-bg appears on the next build.

borrowed one component’s hook, read by another 3

One component's declared hook, read by another. The borrower inherits the owner's theming: re-skin the owner and the borrower moves with it, silently. Either promote it to a group surface with a name that covers both, or give the borrower its own hook.

TokenDeclared underReaders
--button-radius-md esa-button 2 esa-button, esa-button-group
--loading-spinner-color esa-loading-spinner 2 esa-loading-overlay, esa-loading-spinner
--loading-spinner-track-color esa-loading-spinner 2 esa-loading-overlay, esa-loading-spinner
group-surface a family surface, shared by design 15

Declared under a family header so several components align on one scale — the pattern SPEC.md prefers over duplicating a knob per component. Working as designed, and NOT a tier-2 candidate: tier 2 holds intent, a group surface holds coordination scoped to a family.

TokenDeclared underReaders
--focus-ring-width focus ring group 36 esa-alert-box, esa-app-shell, esa-back-to-top, esa-button, esa-button-toggle, esa-checkbox, esa-checkbox-group, esa-chip-group, esa-color-picker, esa-combobox, esa-command-palette, esa-confirm-dialog, esa-date-picker, esa-dialog, esa-dropdown-menu, esa-entity-search, esa-error-summary, esa-file-list, esa-file-upload, esa-filter-clear-button, esa-filter-dropdown, esa-filter-pills, esa-header-nav, esa-input-tag, esa-pagination, esa-pill, esa-radio-group, esa-range-slider, esa-search-panel, esa-select, esa-sidebar-nav, esa-snackbar-item, esa-switch-toggle, esa-tab-layout, esa-text-field, esa-textarea
--focus-ring-color focus ring group 35 esa-alert-box, esa-app-shell, esa-back-to-top, esa-button, esa-button-toggle, esa-checkbox, esa-checkbox-group, esa-chip-group, esa-color-picker, esa-combobox, esa-command-palette, esa-confirm-dialog, esa-date-picker, esa-dialog, esa-dropdown-menu, esa-entity-search, esa-error-summary, esa-file-list, esa-file-upload, esa-filter-clear-button, esa-filter-dropdown, esa-filter-pills, esa-header-nav, esa-input-tag, esa-pagination, esa-pill, esa-radio-group, esa-range-slider, esa-search-panel, esa-select, esa-sidebar-nav, esa-switch-toggle, esa-tab-layout, esa-text-field, esa-textarea
--focus-ring-offset focus ring group 32 esa-alert-box, esa-app-shell, esa-back-to-top, esa-button, esa-button-toggle, esa-checkbox, esa-checkbox-group, esa-chip-group, esa-color-picker, esa-combobox, esa-command-palette, esa-confirm-dialog, esa-date-picker, esa-dialog, esa-entity-search, esa-error-summary, esa-file-upload, esa-filter-clear-button, esa-filter-dropdown, esa-header-nav, esa-input-tag, esa-pagination, esa-pill, esa-radio-group, esa-range-slider, esa-search-panel, esa-select, esa-sidebar-nav, esa-snackbar-item, esa-switch-toggle, esa-text-field, esa-textarea
--form-border-color forms group 12 esa-button-toggle, esa-checkbox, esa-checkbox-group, esa-color-picker, esa-combobox, esa-date-picker, esa-file-upload, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-border-color-focus forms group 12 esa-checkbox, esa-checkbox-group, esa-color-picker, esa-combobox, esa-date-picker, esa-file-upload, esa-filter-dropdown, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-border-width forms group 11 esa-button-toggle, esa-checkbox, esa-checkbox-group, esa-color-picker, esa-combobox, esa-date-picker, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-error-border-color forms group 10 esa-button-toggle, esa-checkbox-group, esa-combobox, esa-date-picker, esa-form-field, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-error-color forms group 10 esa-button-toggle, esa-checkbox-group, esa-combobox, esa-date-picker, esa-form-field, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-help-color forms group 10 esa-button-toggle, esa-checkbox-group, esa-combobox, esa-date-picker, esa-form-field, esa-input-tag, esa-radio-group, esa-select, esa-text-field, esa-textarea
--form-label-color forms group 8 esa-button-toggle, esa-combobox, esa-date-picker, esa-form-field, esa-input-tag, esa-select, esa-text-field, esa-textarea
--form-text-color forms group 7 esa-color-picker, esa-combobox, esa-date-picker, esa-input-tag, esa-select, esa-text-field, esa-textarea
--form-help-gap forms group 6 esa-checkbox-group, esa-field-error, esa-form-field, esa-radio-group, esa-text-field, esa-textarea
--form-placeholder-color forms group 5 esa-combobox, esa-input-tag, esa-select, esa-text-field, esa-textarea
--form-border-color-hover forms group 4 esa-input-tag, esa-select, esa-text-field, esa-textarea
--form-label-gap forms group 3 esa-form-field, esa-text-field, esa-textarea

Under-promise — read, declared nowhere

The inverse failure. These hooks work — the component reads them with an inline fallback — but no token file declares them, so nothing outside the component’s own source records that they exist. They show on the component’s Theming surface table as tier ad-hoc; they do not show in component-tokens.css, which is where a spoke author looks first.

This panel used to add “and SPEC.md permits the pattern”. It does not. Its only mention of ad-hoc gives two dispositions and no third — promote into component-tokens.css, or fold away — and reading a permission into that turned a missing declaration into a fourth tier. There are three. An undeclared read is a tier-3 token that never got its line written.

Empty is the expected state. The 30 that stood here were resolved on 2026-08-16 (9 declared, 24 folded). A row below means someone has added an undeclared read since — it wants one of the same two answers, not a third.