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.
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
| Token | Resolves to |
|---|---|
--grid-row-bg | #fcfcfc |
--grid-row-bg-hover | #f0f0f0 |
--grid-row-bg-selected | color-mix(in srgb, #46a758 6%, transparent) |
--grid-row-border-color | #d9d9d9 |
--grid-row-font-size | clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem) |
--grid-row-stripe-bg | #fcfcfc |
no component header 0/5 of this part read
| Token | Resolves to |
|---|---|
--grid-header-bg | #fcfcfc |
--grid-header-border-color | #cecece |
--grid-header-color | #202020 |
--grid-header-font-size | clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) |
--grid-header-weight | 550 |
no component toolbar 0/3 of this part read
| Token | Resolves to |
|---|---|
--grid-toolbar-bg | #fcfcfc |
--grid-toolbar-gap | 0.5rem |
--grid-toolbar-padding | 0.5rem 0.75rem |
no component cell 0/2 of this part read
| Token | Resolves to |
|---|---|
--grid-cell-padding-x | 0.75rem |
--grid-cell-padding-y | 0.5rem |
no component font 0/2 of this part read
| Token | Resolves to |
|---|---|
--grid-font-family | 'DM Sans', sans-serif |
--grid-font-size | clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) |
no component pagination 0/2 of this part read
| Token | Resolves to |
|---|---|
--grid-pagination-bg | #fcfcfc |
--grid-pagination-border-color | #cecece |
no component bg 0/1 of this part read
| Token | Resolves to |
|---|---|
--grid-bg | #fcfcfc |
no component border 0/1 of this part read
| Token | Resolves to |
|---|---|
--grid-border-color | #cecece |
no component radius 0/1 of this part read
| Token | Resolves to |
|---|---|
--grid-radius | 0.5rem |
no component shadow 0/1 of this part read
| Token | Resolves to |
|---|---|
--grid-shadow | 0 |
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.
| Token | Declared under | Readers |
|---|---|---|
--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.
| Token | Declared under | Readers |
|---|---|---|
--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.