@signal9/era-ui 30.8.0 → 32.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/ai/artifact/artifact.svelte +1 -1
  3. package/dist/ai/conversations/conversations-item.svelte +1 -1
  4. package/dist/ai/plan/plan-header.svelte +1 -1
  5. package/dist/ai/prompt-input/prompt-input-attachments.svelte +2 -6
  6. package/dist/ai/swarm/swarm-node.svelte +1 -1
  7. package/dist/ai/todo/todo-header.svelte +1 -1
  8. package/dist/ai/todo/todo-item.svelte +1 -1
  9. package/dist/ai/tool/tool-chip.svelte +1 -1
  10. package/dist/ai/web-preview/web-preview.svelte +2 -2
  11. package/dist/ai/workflow/node.svelte +1 -1
  12. package/dist/dev/audit/audits/wall-is-isotropic.js +1 -1
  13. package/dist/docs/css-utilities.js +9 -5
  14. package/dist/era-ui.css +1 -1
  15. package/dist/generated-docs/badge.md +1 -1
  16. package/dist/generated-docs/bar.md +22 -4
  17. package/dist/generated-docs/button.md +38 -4
  18. package/dist/generated-docs/card.md +35 -5
  19. package/dist/generated-docs/chip.md +16 -0
  20. package/dist/generated-docs/llms-full.txt +370 -70
  21. package/dist/generated-docs/llms.txt +8 -4
  22. package/dist/generated-docs/manifest.json +85 -28
  23. package/dist/generated-docs/meter.md +79 -1
  24. package/dist/generated-docs/nav.md +8 -1
  25. package/dist/generated-docs/separator.md +18 -0
  26. package/dist/generated-docs/utilities.json +68 -15
  27. package/dist/generated-docs/utilities.md +145 -50
  28. package/dist/os/notification-center.svelte +1 -1
  29. package/dist/os/taskbar.svelte +1 -1
  30. package/dist/os/toast.svelte +1 -1
  31. package/dist/styles/density.css +15 -25
  32. package/dist/styles/index.css +74 -48
  33. package/dist/styles/motion.css +1 -1
  34. package/dist/styles/prose.css +1 -1
  35. package/dist/styles/surfaces/bevel.css +16 -19
  36. package/dist/styles/surfaces/contract.css +50 -0
  37. package/dist/styles/surfaces/flat.css +27 -0
  38. package/dist/styles/surfaces/glass.css +11 -8
  39. package/dist/ui/accordion/accordion-trigger.svelte +1 -1
  40. package/dist/ui/alert-dialog/alert-dialog-content.svelte +1 -1
  41. package/dist/ui/badge/badge.svelte +8 -2
  42. package/dist/ui/badge/badge.svelte.d.ts +1 -1
  43. package/dist/ui/bar/bar.svelte +95 -26
  44. package/dist/ui/bar/bar.svelte.d.ts +71 -31
  45. package/dist/ui/button/button.svelte +6 -0
  46. package/dist/ui/button/button.svelte.d.ts +2 -0
  47. package/dist/ui/button/variants.d.ts +3 -3
  48. package/dist/ui/button/variants.js +6 -11
  49. package/dist/ui/calendar/calendar-day.svelte +1 -1
  50. package/dist/ui/card/card.svelte +23 -10
  51. package/dist/ui/card/card.svelte.d.ts +19 -7
  52. package/dist/ui/chip/chip.svelte +5 -2
  53. package/dist/ui/chip/chip.svelte.d.ts +6 -6
  54. package/dist/ui/collapsible/collapsible-trigger.svelte +1 -1
  55. package/dist/ui/combobox/combobox-content.svelte +1 -1
  56. package/dist/ui/combobox/combobox-trigger.svelte +1 -1
  57. package/dist/ui/command/command-item.svelte +1 -1
  58. package/dist/ui/command/command-link-item.svelte +1 -1
  59. package/dist/ui/command/command-root.svelte +5 -7
  60. package/dist/ui/command-bar/command-bar.svelte +3 -3
  61. package/dist/ui/context-menu/context-menu-content.svelte +1 -4
  62. package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -4
  63. package/dist/ui/date-field/date-field-segment.svelte +1 -1
  64. package/dist/ui/date-picker/date-picker-content.svelte +1 -1
  65. package/dist/ui/date-picker/date-picker-day.svelte +1 -1
  66. package/dist/ui/date-picker/date-picker-segment.svelte +1 -1
  67. package/dist/ui/date-picker/date-picker-trigger.svelte +1 -1
  68. package/dist/ui/date-range-field/date-range-field-segment.svelte +1 -1
  69. package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
  70. package/dist/ui/date-range-picker/date-range-picker-day.svelte +1 -1
  71. package/dist/ui/date-range-picker/date-range-picker-segment.svelte +1 -1
  72. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +1 -1
  73. package/dist/ui/dialog/dialog-content.svelte +20 -2
  74. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  75. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -4
  76. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -1
  77. package/dist/ui/file-upload/file-upload.svelte +1 -1
  78. package/dist/ui/input/variants.d.ts +6 -6
  79. package/dist/ui/input/variants.js +2 -2
  80. package/dist/ui/kv/kv.svelte +1 -1
  81. package/dist/ui/link-preview/link-preview-content.svelte +1 -1
  82. package/dist/ui/menubar/menubar-content.svelte +1 -1
  83. package/dist/ui/menubar/menubar-root.svelte +1 -1
  84. package/dist/ui/menubar/menubar-sub-content.svelte +1 -4
  85. package/dist/ui/menubar/menubar-trigger.svelte +1 -1
  86. package/dist/ui/meter/meter.svelte +1 -2
  87. package/dist/ui/nav/nav-bar.svelte +9 -4
  88. package/dist/ui/nav/nav-bar.svelte.d.ts +2 -1
  89. package/dist/ui/nav/nav-item.svelte +1 -0
  90. package/dist/ui/pane/pane-root.svelte +3 -12
  91. package/dist/ui/pane/pane-tab.svelte +1 -1
  92. package/dist/ui/popover/popover-content.svelte +1 -1
  93. package/dist/ui/provider/surfaces.d.ts +1 -1
  94. package/dist/ui/provider/surfaces.js +1 -1
  95. package/dist/ui/select/select-content.svelte +1 -1
  96. package/dist/ui/select/select-trigger.svelte +1 -1
  97. package/dist/ui/sheet/sheet-content.svelte +1 -1
  98. package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
  99. package/dist/ui/slider/slider-thumb.svelte +1 -1
  100. package/dist/ui/step/step-summary.svelte +1 -1
  101. package/dist/ui/switch/switch.svelte +3 -3
  102. package/dist/ui/time-field/time-field-segment.svelte +1 -1
  103. package/dist/ui/time-range-field/time-range-field-segment.svelte +1 -1
  104. package/dist/ui/timeline/timeline-step.svelte +1 -1
  105. package/dist/ui/toggle-group/toggle-group-item.svelte +2 -2
  106. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +6 -6
  107. package/dist/ui/toggle-group/toggle-group-root.svelte +1 -2
  108. package/dist/ui/toggle-group/toggle-group-root.svelte.d.ts +3 -3
  109. package/dist/ui/tooltip/tooltip-content.svelte +1 -1
  110. package/dist/ui/tree/tree-item.svelte +1 -1
  111. package/dist/ui/video-player/video-player.svelte +2 -2
  112. package/dist/utils/tw-merge-config.d.ts +6 -1
  113. package/dist/utils/tw-merge-config.js +6 -2
  114. package/package.json +5 -3
  115. package/skill/SKILL.md +26 -21
  116. package/dist/styles/surfaces/base.css +0 -257
@@ -63,9 +63,13 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
63
63
  - `era-ink-center` — on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field — never alongside era-text-trim.
64
64
  - `era-token-ink` — the ink centring for a token pill — a badge, a KV value, any short caps-or-digits label in a fixed-height box, CENTRED horizontally and with no icon sibling. For a left-aligned sentence-case label use era-label-ink; when an icon really must sit beside the text, see the note there.
65
65
  - `era-label-ink` — the ink centring for a fixed-height control holding ONE line of sentence-case text and no icon — a menu item, a nav trigger, a button label. Never alongside `truncate` (the trimmed box becomes the clip).
66
- - `glass-blur` — the backdrop blur for a floating panel (popover, dialog, taskbar) — it applies only inside a data-surface="glass" subtree and is inert elsewhere, so it is safe to leave on a panel that renders under every surface.
67
- - `rounded-row` — the radius for a row inside a container that pads it by the gutter — a nav rail, a command list, any scrolling list of controls. For a row in a menu PANEL, whose inset is the surface-owned frame rather than the gutter, use rounded-menu-item.
66
+ - `surface-backdrop` — the surface-aware backdrop treatment without choosing a fill or relief. Prefer material-popover/material-panel when the element is a complete surface.
67
+ - `material-popover` — a floating transient surface: menus, popovers, tooltips, pickers, toasts. Binds its raised fill, floating relief, and backdrop treatment atomically.
68
+ - `material-panel` — a floating persistent surface: dialogs, sheets, panes, inspectors. Binds its resting fill, floating relief, and backdrop treatment atomically.
69
+ - `pressable` — the physical press contract on a control whose direct content plane is marked data-era-press-content. The surface owns relief/travel; the component may scale travel with --era-press-scale.
70
+ - `tier-icon · tier-pill · tier-chip · tier-control · tier-bar` — BOUNDED GEOMETRY — height and radius are one decision.
68
71
  - `h-line` — the height of one line of the element's own text — a skeleton standing in for a line, a spacer that must match a line box.
72
+ - `era-track-well` — instead of `shadow-well` on a track whose value is painted by a child that covers the box — a progress/meter/slider track, a custom gauge. For a well holding ordinary content, `shadow-well` is still right.
69
73
  - `era-skeleton-pulse` — Skeleton's pulse, on the MOTION AXIS.
70
74
  - `era-shimmer` — the in-progress text treatment — put it on a "working…" / "thinking…" label while a task streams, instead of a spinner beside the text.
71
75
  - `era-collapse` — the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
@@ -86,12 +90,12 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
86
90
  - `bg-well · text-well · border-well · bg-elevated · text-elevated · border-elevated · bg-highlight · text-highlight · … (12 in all)` — Surface fills — bg-well is the surface's own resting fill (pairs with
87
91
  - `p-gutter · px-gutter · py-gutter · gap-gutter · p-panel · px-panel · py-panel · gap-panel · … (24 in all)` — THE SPACING SCALE — one entry per token, from which Tailwind derives the
88
92
  - `p-inset-pill · px-inset-pill · py-inset-pill · gap-inset-pill · p-inset-chip · px-inset-chip · py-inset-chip · gap-inset-chip · … (12 in all)` — text-symmetric content padding — (tier − text)/2, the inset that vertically centres body text in its tier; px-inset-control is a text button's side padding, p-inset-control a code block's.
89
- - `p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (36 in all)` — the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control.
93
+ - `p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (40 in all)` — the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control.
90
94
  - `p-glyph-icon · px-glyph-icon · py-glyph-icon · gap-glyph-icon · p-glyph-pill · px-glyph-pill · py-glyph-pill · gap-glyph-pill` — the glyph size for a tier — the INK inside the box, not the box (size-glyph-pill is the dismiss × in a pill, size-pill is the pill).
91
95
  - `bg-field · text-field · border-field · bg-field-highlight · text-field-highlight · border-field-highlight` — the recessed field fill — bg-field is a text input's resting well, bg-field-highlight its focused/hovered face.
92
96
  - `ease-base` — Motion — bare transition utilities default to the motion axis, so
93
97
  - `bg-divider · text-divider · border-divider · bg-divider-faded · text-divider-faded · border-divider-faded · bg-background · text-background · … (66 in all)` — Surface divider colors: border-divider, bg-divider-faded, …
94
- - `bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (33 in all)` — The standing pill fill — --era-fill, the latched counterpart of the
98
+ - `bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (39 in all)` — The standing pill fill — --era-fill, the latched counterpart of the
95
99
 
96
100
  Full reference (what each emits, when to use it): `/utilities.md`. Machine-readable list: `/utilities.json`.
97
101
 
@@ -153,7 +157,7 @@ The CSS classes era ships alongside the components — links, ink-centred text,
153
157
 
154
158
  ## Overview
155
159
 
156
- era's styling API is pure class names — write `h-control rounded-control px-gutter shadow`,
160
+ era's styling API is pure class names — write `tier-control px-gutter shadow`,
157
161
  never the `h-(--era-h-control)` var-class spelling. Everything below rides along
158
162
  with the stylesheet and needs no import:
159
163
 
@@ -162,7 +166,7 @@ import "@signal9/era-ui/css";
162
166
  ```
163
167
 
164
168
  ```svelte
165
- <button class="h-control rounded-control px-inset-control shadow text-body">era styled</button>
169
+ <button class="tier-control px-inset-control shadow text-body">era styled</button>
166
170
  ```
167
171
 
168
172
  Alongside the `@utility` classes referenced below, era registers named theme
@@ -172,6 +176,7 @@ motion):
172
176
 
173
177
  | Stock-looking class | What it resolves to |
174
178
  |---|---|
179
+ | `tier-icon/pill/chip/control/bar` | indivisible bounded height + concentric radius |
175
180
  | `rounded-icon/pill/chip/control/bar` | the concentric radius ladder (collapses on `data-corners="square"`) |
176
181
  | `rounded-menu-item` | menu-row radius, concentric inside a `rounded-control` panel |
177
182
  | `shadow` / `shadow-lg` | resting control edge / floating panel chrome, per surface |
@@ -197,10 +202,13 @@ The spacing names (`gutter`, `card`, `panel`, `content`, `menu`, `field`, the
197
202
  `inset-*` and concentric `*-inset-*` insets, `glyph-*`) are registered as a
198
203
  Tailwind spacing scale, so every prefix derives automatically: `p-`, `px-`,
199
204
  `pt-`, `mt-`, `gap-`, `gap-x-`, `inset-`, `translate-`, and the rest. The TIER
200
- names (`icon`, `pill`, `chip`, `control`, `bar`) are heights, not spacing, so
205
+ names (`icon`, `pill`, `chip`, `control`, `bar`) are dimensions, not spacing, so
201
206
  they are registered per prefix instead — `h-`, `w-`, `min-h-`, `min-w-` and
202
207
  `size-` — and the two vocabularies deliberately share no word. Putting a tier
203
208
  in the spacing scale would silently make `px-control` mean 24px of padding.
209
+ For chrome, use `tier-T`, which binds `h-T` and `rounded-T`; use the separate
210
+ dimension/radius names only for width, minimums, unbounded surfaces, or
211
+ asymmetric/docked corners.
204
212
 
205
213
  The var form (`h-(--era-h-control)`) still works and is the escape hatch for a
206
214
  token with no registered name — `--era-sp`, the press-sink offsets, anything
@@ -218,9 +226,13 @@ source uses it where a name exists.
218
226
  | `era-ink-center` | on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field — never alongside era-text-trim. |
219
227
  | `era-token-ink` | the ink centring for a token pill — a badge, a KV value, any short caps-or-digits label in a fixed-height box, CENTRED horizontally and with no icon sibling. For a left-aligned sentence-case label use era-label-ink; when an icon really must sit beside the text, see the note there. |
220
228
  | `era-label-ink` | the ink centring for a fixed-height control holding ONE line of sentence-case text and no icon — a menu item, a nav trigger, a button label. Never alongside `truncate` (the trimmed box becomes the clip). |
221
- | `glass-blur` | the backdrop blur for a floating panel (popover, dialog, taskbar) — it applies only inside a data-surface="glass" subtree and is inert elsewhere, so it is safe to leave on a panel that renders under every surface. |
222
- | `rounded-row` | the radius for a row inside a container that pads it by the gutter — a nav rail, a command list, any scrolling list of controls. For a row in a menu PANEL, whose inset is the surface-owned frame rather than the gutter, use rounded-menu-item. |
229
+ | `surface-backdrop` | the surface-aware backdrop treatment without choosing a fill or relief. Prefer material-popover/material-panel when the element is a complete surface. |
230
+ | `material-popover` | a floating transient surface: menus, popovers, tooltips, pickers, toasts. Binds its raised fill, floating relief, and backdrop treatment atomically. |
231
+ | `material-panel` | a floating persistent surface: dialogs, sheets, panes, inspectors. Binds its resting fill, floating relief, and backdrop treatment atomically. |
232
+ | `pressable` | the physical press contract on a control whose direct content plane is marked data-era-press-content. The surface owns relief/travel; the component may scale travel with --era-press-scale. |
233
+ | `tier-icon` `tier-pill` `tier-chip` `tier-control` `tier-bar` | — |
223
234
  | `h-line` | the height of one line of the element's own text — a skeleton standing in for a line, a spacer that must match a line box. |
235
+ | `era-track-well` | instead of `shadow-well` on a track whose value is painted by a child that covers the box — a progress/meter/slider track, a custom gauge. For a well holding ordinary content, `shadow-well` is still right. |
224
236
  | `era-skeleton-pulse` | — |
225
237
  | `era-shimmer` | the in-progress text treatment — put it on a "working…" / "thinking…" label while a task streams, instead of a spinner beside the text. |
226
238
  | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
@@ -241,12 +253,12 @@ source uses it where a name exists.
241
253
  | `bg-well` `text-well` `border-well` `bg-elevated` `text-elevated` `border-elevated` `bg-highlight` `text-highlight` `border-highlight` `bg-overlay` `text-overlay` `border-overlay` | — |
242
254
  | `p-gutter` `px-gutter` `py-gutter` `gap-gutter` `p-panel` `px-panel` `py-panel` `gap-panel` `p-card` `px-card` `py-card` `gap-card` `p-content` `px-content` `py-content` `gap-content` `p-menu` `px-menu` `py-menu` `gap-menu` `p-field` `px-field` `py-field` `gap-field` | — |
243
255
  | `p-inset-pill` `px-inset-pill` `py-inset-pill` `gap-inset-pill` `p-inset-chip` `px-inset-chip` `py-inset-chip` `gap-inset-chip` `p-inset-control` `px-inset-control` `py-inset-control` `gap-inset-control` | text-symmetric content padding — (tier − text)/2, the inset that vertically centres body text in its tier; px-inset-control is a text button's side padding, p-inset-control a code block's. |
244
- | `p-icon-inset-pill` `px-icon-inset-pill` `py-icon-inset-pill` `gap-icon-inset-pill` `p-icon-inset-chip` `px-icon-inset-chip` `py-icon-inset-chip` `gap-icon-inset-chip` `p-icon-inset-control` `px-icon-inset-control` `py-icon-inset-control` `gap-icon-inset-control` `p-icon-inset-bar` `px-icon-inset-bar` `py-icon-inset-bar` `gap-icon-inset-bar` `p-pill-inset-chip` `px-pill-inset-chip` `py-pill-inset-chip` `gap-pill-inset-chip` `p-pill-inset-control` `px-pill-inset-control` `py-pill-inset-control` `gap-pill-inset-control` `p-pill-inset-bar` `px-pill-inset-bar` `py-pill-inset-bar` `gap-pill-inset-bar` `p-chip-inset-control` `px-chip-inset-control` `py-chip-inset-control` `gap-chip-inset-control` `p-chip-inset-bar` `px-chip-inset-bar` `py-chip-inset-bar` `gap-chip-inset-bar` | the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control. |
256
+ | `p-icon-inset-pill` `px-icon-inset-pill` `py-icon-inset-pill` `gap-icon-inset-pill` `p-icon-inset-chip` `px-icon-inset-chip` `py-icon-inset-chip` `gap-icon-inset-chip` `p-icon-inset-control` `px-icon-inset-control` `py-icon-inset-control` `gap-icon-inset-control` `p-icon-inset-bar` `px-icon-inset-bar` `py-icon-inset-bar` `gap-icon-inset-bar` `p-pill-inset-chip` `px-pill-inset-chip` `py-pill-inset-chip` `gap-pill-inset-chip` `p-pill-inset-control` `px-pill-inset-control` `py-pill-inset-control` `gap-pill-inset-control` `p-pill-inset-bar` `px-pill-inset-bar` `py-pill-inset-bar` `gap-pill-inset-bar` `p-chip-inset-control` `px-chip-inset-control` `py-chip-inset-control` `gap-chip-inset-control` `p-chip-inset-bar` `px-chip-inset-bar` `py-chip-inset-bar` `gap-chip-inset-bar` `p-control-inset-bar` `px-control-inset-bar` `py-control-inset-bar` `gap-control-inset-bar` | the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control. |
245
257
  | `p-glyph-icon` `px-glyph-icon` `py-glyph-icon` `gap-glyph-icon` `p-glyph-pill` `px-glyph-pill` `py-glyph-pill` `gap-glyph-pill` | the glyph size for a tier — the INK inside the box, not the box (size-glyph-pill is the dismiss × in a pill, size-pill is the pill). |
246
258
  | `bg-field` `text-field` `border-field` `bg-field-highlight` `text-field-highlight` `border-field-highlight` | the recessed field fill — bg-field is a text input's resting well, bg-field-highlight its focused/hovered face. |
247
259
  | `ease-base` | — |
248
260
  | `bg-divider` `text-divider` `border-divider` `bg-divider-faded` `text-divider-faded` `border-divider-faded` `bg-background` `text-background` `border-background` `bg-foreground` `text-foreground` `border-foreground` `bg-1` `text-1` `border-1` `bg-2` `text-2` `border-2` `bg-3` `text-3` `border-3` `bg-4` `text-4` `border-4` `bg-5` `text-5` `border-5` `bg-6` `text-6` `border-6` `bg-7` `text-7` `border-7` `bg-8` `text-8` `border-8` `bg-9` `text-9` `border-9` `bg-10` `text-10` `border-10` `bg-11` `text-11` `border-11` `bg-12` `text-12` `border-12` `bg-border` `text-border` `border-border` `bg-surface` `text-surface` `border-surface` `bg-hover` `text-hover` `border-hover` `bg-fg` `text-fg` `border-fg` `bg-bright` `text-bright` `border-bright` `bg-muted` `text-muted` `border-muted` | — |
249
- | `bg-fill` `text-fill` `border-fill` `bg-link` `text-link` `border-link` `bg-primary` `text-primary` `border-primary` `bg-primary-fg` `text-primary-fg` `border-primary-fg` `bg-primary-hover` `text-primary-hover` `border-primary-hover` `bg-destructive` `text-destructive` `border-destructive` `bg-destructive-fg` `text-destructive-fg` `border-destructive-fg` `bg-success` `text-success` `border-success` `bg-success-fg` `text-success-fg` `border-success-fg` `bg-warning` `text-warning` `border-warning` `bg-warning-fg` `text-warning-fg` `border-warning-fg` | — |
261
+ | `bg-fill` `text-fill` `border-fill` `bg-link` `text-link` `border-link` `bg-primary` `text-primary` `border-primary` `bg-primary-fg` `text-primary-fg` `border-primary-fg` `bg-primary-hover` `text-primary-hover` `border-primary-hover` `bg-destructive` `text-destructive` `border-destructive` `bg-destructive-fg` `text-destructive-fg` `border-destructive-fg` `bg-success` `text-success` `border-success` `bg-success-fg` `text-success-fg` `border-success-fg` `bg-warning` `text-warning` `border-warning` `bg-warning-fg` `text-warning-fg` `border-warning-fg` `bg-info` `text-info` `border-info` `bg-info-fg` `text-info-fg` `border-info-fg` | — |
250
262
 
251
263
  ## era-interactive
252
264
 
@@ -599,64 +611,104 @@ Declared in `index.css`.
599
611
  }
600
612
  ```
601
613
 
602
- ## glass-blur
614
+ ## surface-backdrop
603
615
 
604
- **Use it for:** the backdrop blur for a floating panel (popover, dialog, taskbar) — it applies only inside a data-surface="glass" subtree and is inert elsewhere, so it is safe to leave on a panel that renders under every surface.
616
+ **Use it for:** the surface-aware backdrop treatment without choosing a fill or relief. Prefer material-popover/material-panel when the element is a complete surface.
605
617
 
606
- Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
607
- is a registered utility, gated to glass subtrees.
618
+ Declared in `index.css`.
608
619
 
609
- It lives HERE rather than in surfaces/glass.css, where a surface's own
610
- chrome would naturally sit, because glass.css is imported into a cascade
611
- layer and `@utility` cannot be nested inside one. Surface-scoped RULES stay
612
- in their surface file; surface-scoped UTILITIES have to come out.
620
+ ```css
621
+ @utility surface-backdrop {
622
+ backdrop-filter: var(--era-backdrop-filter);
623
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
624
+ }
625
+ ```
626
+
627
+ ## material-popover
628
+
629
+ **Use it for:** a floating transient surface: menus, popovers, tooltips, pickers, toasts. Binds its raised fill, floating relief, and backdrop treatment atomically.
613
630
 
614
631
  Declared in `index.css`.
615
632
 
616
633
  ```css
617
- @utility` cannot be nested inside one. Surface-scoped RULES stay
618
- * in their surface file; surface-scoped UTILITIES have to come out.
619
- *
620
- * @use the backdrop blur for a floating panel (popover, dialog, taskbar) — it
621
- * applies only inside a data-surface="glass" subtree and is inert elsewhere,
622
- * so it is safe to leave on a panel that renders under every surface. */
623
- @utility glass-blur {
624
- [data-surface='glass'] & {
625
- backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
626
- -webkit-backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
627
- }
634
+ @utility material-popover {
635
+ background-color: var(--era-surface-bg-elevated);
636
+ box-shadow: var(--era-shadow-lg);
637
+ backdrop-filter: var(--era-backdrop-filter);
638
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
628
639
  }
629
640
  ```
630
641
 
631
- ## rounded-row
642
+ ## material-panel
632
643
 
633
- **Use it for:** the radius for a row inside a container that pads it by the gutter — a nav rail, a command list, any scrolling list of controls. For a row in a menu PANEL, whose inset is the surface-owned frame rather than the gutter, use rounded-menu-item.
644
+ **Use it for:** a floating persistent surface: dialogs, sheets, panes, inspectors. Binds its resting fill, floating relief, and backdrop treatment atomically.
634
645
 
635
- THE ROW RADIUS — concentricity for a row that is inset, WITH its inset intact.
646
+ Declared in `index.css`.
636
647
 
637
- The law is rd_outer − rd_inner = gap. A row inside a container that pads it by
638
- the gutter therefore takes the container's radius MINUS the gutter — and no
639
- tier radius equals that at more than one density, which is why reaching for
640
- `rounded-control` and hoping looks wrong: in a control-radius pane the row and
641
- the pane end up with identical curvature separated by 5px of gap, two arcs at
642
- one corner with no relationship.
648
+ ```css
649
+ @utility material-panel {
650
+ background-color: var(--era-surface-bg);
651
+ box-shadow: var(--era-shadow-lg);
652
+ backdrop-filter: var(--era-backdrop-filter);
653
+ -webkit-backdrop-filter: var(--era-backdrop-filter);
654
+ }
655
+ ```
643
656
 
644
- The alternative — deleting the gap so equal radii become legal — is the wrong
645
- trade and was briefly shipped. The ladder exists so a gap and concentric
646
- corners COEXIST; spending one to buy the other gives up the thing it is for.
657
+ ## pressable
647
658
 
648
- The calc runs on the ROW, not on the container, which is what makes it
649
- automatic: `--era-container-rd` is inherited, so a container that is not
650
- control-radius publishes its own and every row inside re-derives. era's
651
- command palette does exactly that — it is rd-bar, so its rows resolve to
652
- rd-bar − gap, which IS rd-control (control-inset-bar is the gutter), and the
653
- palette's geometry is unchanged by this.
659
+ **Use it for:** the physical press contract on a control whose direct content plane is marked data-era-press-content. The surface owns relief/travel; the component may scale travel with --era-press-scale.
654
660
 
655
661
  Declared in `index.css`.
656
662
 
657
663
  ```css
658
- @utility rounded-row {
659
- border-radius: max(0px, calc(var(--era-container-rd, var(--era-rd-control)) - var(--era-gap)));
664
+ @utility pressable {
665
+ &:active {
666
+ box-shadow: var(--era-shadow-pressed);
667
+ }
668
+
669
+ &:active > [data-era-press-content] {
670
+ translate: var(--era-press-effective-x, var(--era-press-sink-x))
671
+ var(--era-press-effective-y, var(--era-press-sink-y));
672
+ }
673
+ }
674
+ ```
675
+
676
+ ## tier-icon · tier-pill · tier-chip · tier-control · tier-bar
677
+
678
+ BOUNDED GEOMETRY — height and radius are one decision.
679
+
680
+ A tier is a geometric type, not two classes a caller must remember to keep in
681
+ sync. `tier-control` establishes h-control AND rd-control; the radius ladder
682
+ then guarantees rd_outer − rd_inner = (h_outer − h_inner)/2 for every pair.
683
+ Width remains the component's decision: a row stretches, a button adds
684
+ min-w-control, and a square control adds w-control.
685
+
686
+ Declared in `index.css`.
687
+
688
+ ```css
689
+ @utility tier-icon {
690
+ height: var(--era-h-icon);
691
+ border-radius: var(--era-rd-icon);
692
+ }
693
+
694
+ @utility tier-pill {
695
+ height: var(--era-h-pill);
696
+ border-radius: var(--era-rd-pill);
697
+ }
698
+
699
+ @utility tier-chip {
700
+ height: var(--era-h-chip);
701
+ border-radius: var(--era-rd-chip);
702
+ }
703
+
704
+ @utility tier-control {
705
+ height: var(--era-h-control);
706
+ border-radius: var(--era-rd-control);
707
+ }
708
+
709
+ @utility tier-bar {
710
+ height: var(--era-h-bar);
711
+ border-radius: var(--era-rd-bar);
660
712
  }
661
713
  ```
662
714
 
@@ -681,6 +733,50 @@ Declared in `index.css`.
681
733
  }
682
734
  ```
683
735
 
736
+ ## era-track-well
737
+
738
+ **Use it for:** instead of `shadow-well` on a track whose value is painted by a child that covers the box — a progress/meter/slider track, a custom gauge. For a well holding ordinary content, `shadow-well` is still right.
739
+
740
+ A WELL THAT SURVIVES BEING FILLED.
741
+
742
+ `shadow-well` is an INSET shadow, and an inset shadow belongs to the
743
+ element's background layer — it paints under the element's own descendants.
744
+ That is invisible on a well holding text (a checkbox, a code block: the
745
+ chrome runs around the content) and wrong the moment something paints the
746
+ whole box. Meter, Progress and the Slider track all do exactly that: an
747
+ opaque child, `translateX`-ed to the value. So the well survived only where
748
+ the track was EMPTY — a half-full meter on bevel had a chisel to the right
749
+ of the value and none to the left, which reads as a hole with a sticker
750
+ over it rather than a level inside a well.
751
+
752
+ The fix is painting order, not geometry: the same `--era-shadow-well` on a
753
+ pseudo-element that is the element's LAST child, so it lands above the fill.
754
+ The fill still spans the full box — nothing is inset, no radius is
755
+ re-derived, and 0% and 100% keep their exact ends. The `position: relative`
756
+ is load-bearing (the overlay resolves against this box) and `inherit` on the
757
+ radius keeps it honest under `data-corners="square"`.
758
+
759
+ Surface-agnostic on purpose. It reads the same token every well reads, so
760
+ glass's hairline rim is restored by the same line that restores bevel's
761
+ chisel, and flat — where the token is `none` — renders exactly as before.
762
+ Reported against bevel by a consumer; it was never a bevel bug.
763
+
764
+ Declared in `index.css`.
765
+
766
+ ```css
767
+ @utility era-track-well {
768
+ position: relative;
769
+ &::after {
770
+ content: '';
771
+ position: absolute;
772
+ inset: 0;
773
+ border-radius: inherit;
774
+ box-shadow: var(--era-shadow-well);
775
+ pointer-events: none;
776
+ }
777
+ }
778
+ ```
779
+
684
780
  ## era-skeleton-pulse
685
781
 
686
782
  Skeleton's pulse, on the MOTION AXIS.
@@ -1106,8 +1202,8 @@ Declared in `index.css`.
1106
1202
  ## bg-well · text-well · border-well · bg-elevated · text-elevated · border-elevated · bg-highlight · text-highlight · … (12 in all)
1107
1203
 
1108
1204
  Surface fills — bg-well is the surface's own resting fill (pairs with
1109
- shadow-well for recessed chrome), bg-elevated the raised tier (pairs with
1110
- shadow-lg + glass-blur for floating chrome), bg-highlight the transient
1205
+ shadow-well for recessed chrome), bg-elevated the raised tier,
1206
+ bg-highlight the transient
1111
1207
  hover/focus/keyboard wash (pairs with shadow-highlight; latched states
1112
1208
  stay on opaque bg-hover), bg-overlay the modal scrim.
1113
1209
 
@@ -1170,7 +1266,7 @@ Declared in `index.css`.
1170
1266
  }
1171
1267
  ```
1172
1268
 
1173
- ## p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (36 in all)
1269
+ ## p-icon-inset-pill · px-icon-inset-pill · py-icon-inset-pill · gap-icon-inset-pill · p-icon-inset-chip · px-icon-inset-chip · py-icon-inset-chip · gap-icon-inset-chip · … (40 in all)
1174
1270
 
1175
1271
  **Use it for:** the concentric inset for a nested pair — (h-container − h-child)/2, the gap that makes a child control sit evenly inside its container, and the radius step that keeps their corners concentric. Named for the pair it joins: gap-icon-inset-control seats an icon in a control.
1176
1272
 
@@ -1187,6 +1283,7 @@ Declared in `index.css`.
1187
1283
  --spacing-pill-inset-bar: var(--era-pill-inset-bar);
1188
1284
  --spacing-chip-inset-control: var(--era-chip-inset-control);
1189
1285
  --spacing-chip-inset-bar: var(--era-chip-inset-bar);
1286
+ --spacing-control-inset-bar: var(--era-control-inset-bar);
1190
1287
  }
1191
1288
  ```
1192
1289
 
@@ -1265,7 +1362,7 @@ Declared in `index.css`.
1265
1362
  }
1266
1363
  ```
1267
1364
 
1268
- ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (33 in all)
1365
+ ## bg-fill · text-fill · border-fill · bg-link · text-link · border-link · bg-primary · text-primary · … (39 in all)
1269
1366
 
1270
1367
  The standing pill fill — --era-fill, the latched counterpart of the
1271
1368
  transient highlight. Surface-owned: a composing wash on flat/bevel, the
@@ -1286,6 +1383,8 @@ Declared in `index.css`.
1286
1383
  --color-success-fg: var(--color-success-fg);
1287
1384
  --color-warning: var(--color-warning);
1288
1385
  --color-warning-fg: var(--color-warning-fg);
1386
+ --color-info: var(--color-info);
1387
+ --color-info-fg: var(--color-info-fg);
1289
1388
  }
1290
1389
  ```
1291
1390
 
@@ -1456,7 +1555,7 @@ Inherits all props from `HTMLAttributes<HTMLSpanElement>`.
1456
1555
  | Prop | Type | Default | Notes |
1457
1556
  |------|------|---------|-------|
1458
1557
  | `ref?` | `HTMLSpanElement \| null` | `null` | bindable |
1459
- | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | — |
1558
+ | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | — |
1460
1559
  | `lead?` | `Snippet` | — | Icon rendered BEFORE the label, outside it. Use this rather than putting
1461
1560
  an icon in `children`: the label then gets its own box, which is the only
1462
1561
  place era's ink centring can reach. |
@@ -1547,10 +1646,28 @@ Inherits all props from `HTMLAttributes<HTMLDivElement>`.
1547
1646
  |------|------|---------|-------|
1548
1647
  | `ref?` | `HTMLDivElement \| null` | `null` | bindable |
1549
1648
  | `size?` | `'chip' \| 'control' \| 'bar'` | `'bar'` | Height tier (scales with density). Default `lg` — a full toolbar. |
1550
- | `content?` | `'tier' \| 'pill'` | `'tier'` | Tier of the controls inside — drives the bar's padding and gap so they
1551
- stay concentric to them. `tier` (default) is one tier smaller; `pill` is
1552
- the compact pairing (pills/fields in an sm or md bar). |
1553
- | `divider?` | `boolean` | `false` | The flush cap-a-panel chrome: outset divider below, bottom rd-md. |
1649
+ | `content?` | `BarContent` | | Tier of the controls inside — drives the bar's padding and gap so they
1650
+ stay concentric to them. Omit this for the next canonical rung, or name
1651
+ the actual tier when the contents are smaller. |
1652
+ | `divider?` | `boolean` | `false` | The flush cap-a-panel chrome: an outset rule below, and a rounded
1653
+ BOTTOM only — the top defers to the panel's own radius.
1654
+ THE BOTTOM RADIUS IS LOAD-BEARING FOR THE RULE. The divider is an
1655
+ outset `box-shadow`, so it follows the box's corners: the rounded
1656
+ bottom is what curves the line up at the ends and makes the bar read
1657
+ as a long pill. Overriding the radius therefore changes the RULE, not
1658
+ just the corners. Measured against `cn`:
1659
+ - `rounded-none` drops both — a square bar with a dead-straight rule.
1660
+ That is the intended square mode (an in-app Notes bar passes it).
1661
+ - `rounded-b-*` or `rounded-b-none` replaces the curve — this is the
1662
+ one that surprises, because the rule changes shape when the caller
1663
+ thought they were only squaring a corner.
1664
+ - `rounded-t-*` is SAFE: it wins against `rounded-t-none` and leaves
1665
+ `rounded-b-control` standing, so the curve survives.
1666
+ A consumer lost the curve this way. The coupling was already written
1667
+ down — on the variant, inside `<script module>`, where someone
1668
+ hovering this prop never reaches it. Docs are only as good as the
1669
+ place the reader is standing. |
1670
+ | `topology?` | `'nested' \| 'docked'` | `'nested'` | `docked` removes one or more exposed curves; strict radius nesting does not apply. |
1554
1671
  | `children?` | `Snippet` | — | — |
1555
1672
 
1556
1673
  <!-- end: bar -->
@@ -1580,7 +1697,7 @@ Inherits all props from `HTMLAttributes<HTMLElement>`.
1580
1697
  |------|------|---------|-------|
1581
1698
  | `ref?` | `HTMLElement \| null` | `null` | bindable |
1582
1699
  | `size?` | `'chip' \| 'control' \| 'bar'` | `'bar'` | Height tier, exactly as Bar's — the geometry is Bar's, not a second copy. |
1583
- | `content?` | `'tier' \| 'pill'` | `'tier'` | Tier of the items inside, so the wall stays concentric to them. |
1700
+ | `content?` | `BarContent` | | Tier of the items inside, so the wall stays concentric to them. |
1584
1701
  | `divider?` | `boolean` | `false` | Draw Bar's flush cap-a-panel rule beneath the bar. Off by default,
1585
1702
  because a nav that sits in a page already has whatever separation the
1586
1703
  layout gives it — and a link-variant nav in particular reads as text,
@@ -1605,6 +1722,13 @@ Inherits all props from `HTMLAnchorAttributes`.
1605
1722
  drops the chrome entirely and renders running text, for a primary nav
1606
1723
  that should read as links rather than a row of raised keycaps. Existing
1607
1724
  bars are byte-identical. |
1725
+ | `currentCue?` | `'thickness' \| 'ink' \| 'none'` | `'thickness'` | WHAT MARKS THE CURRENT ITEM. `thickness` (default) is the three-state
1726
+ decoration — portable across every theme and font because it is one
1727
+ ink at three strengths. `ink` brightens the current item instead, and
1728
+ is theme-dependent by construction: era's default themes point
1729
+ --color-link at --color-bright, so its delta there is zero. `none`
1730
+ silences era's cue entirely, for a consumer painting their own.
1731
+ See navItemVariants above for the measurements behind each. |
1608
1732
  | `current?` | `boolean` | `false` | Whether this item points at the page you are on.
1609
1733
  There is deliberately no `active` prop beside this one. `active` would
1610
1734
  be a second source of truth for one fact, and the two drift the moment
@@ -1644,14 +1768,47 @@ Inherits all props from `Button.RootProps`.
1644
1768
 
1645
1769
  | Prop | Type | Default | Notes |
1646
1770
  |------|------|---------|-------|
1647
- | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | — |
1771
+ | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | — |
1648
1772
  | `size?` | `'control' \| 'chip' \| 'pill' \| 'icon'` | `'control'` | — |
1649
1773
  | `variant?` | `'filled' \| 'link'` | `'filled'` | — |
1650
1774
  | `active?` | `boolean` | `false` | — |
1651
1775
  | `icon?` | `boolean` | `false` | — |
1652
- | `loading?` | `boolean` | `false` | When true, disables the button, sets `aria-busy`, and prepends a
1653
- spinner. The existing label stays rendered so focus, click targets,
1654
- and screen-reader accessible names don't change shape mid-flight. |
1776
+ | `loading?` | `boolean` | `false` | In flight: disables the button, sets `aria-busy`, and puts a spinner
1777
+ over the label.
1778
+ THE GEOMETRY DOES NOT MOVE. That is the requirement, not a detail. A
1779
+ button is loading because it was just CLICKED, so anything `loading`
1780
+ changes about the box moves the page under the pointer that is still
1781
+ on it — and a spinner PREPENDED to the label (what this used to do)
1782
+ costs an icon's width plus a gap, measured on a wrapping header row
1783
+ that reflowed everything beside it. A consumer hit exactly that and
1784
+ stopped using the prop, hand-rolling `disabled` + `aria-busy`
1785
+ instead, which is the report that produced this note.
1786
+ So the spinner is absolutely positioned and takes no part in the
1787
+ flex layout, and the label goes to `opacity-0` rather than being
1788
+ unmounted: the box that holds the width stays, and the text stays in
1789
+ the accessibility tree as the button's accessible name.
1790
+ The visible label is therefore HIDDEN in flight — the one thing this
1791
+ trades away, and the reason `loadingLabel` exists.
1792
+ IF YOU DO NOT WANT A SPINNER, use `busy`. Not showing one is a
1793
+ supported posture, not a legacy one — see that prop. |
1794
+ | `busy?` | `boolean` | `false` | In flight, WITHOUT a spinner: disables the button and sets `aria-busy`,
1795
+ and changes nothing else. The label stays visible and legible.
1796
+ This exists because "show a spinner" is a taste, not a requirement,
1797
+ and era had only one answer. A consumer whose house style is that
1798
+ spinners should not appear had to spell the posture out themselves —
1799
+ `disabled` plus a restated `aria-busy` — and rely on knowing that
1800
+ `restProps` spreads last, which is a real fact about this component
1801
+ that no reader should have to depend on. Two words for two postures
1802
+ is clearer than one word plus a workaround.
1803
+ `loading` implies this: it is `busy` plus the spinner and the hidden
1804
+ label. What you get from `busy` alone is a control that is visibly
1805
+ disabled (the era-interactive opacity) and announced as busy, with
1806
+ its geometry and its ink untouched — which is also why it needs no
1807
+ `loadingLabel`: nothing about the button changed except that you
1808
+ cannot press it.
1809
+ Reported by a consumer whose user asked for no spinners anywhere.
1810
+ They were right that a fix for the width shift did not answer it: a
1811
+ spinner that no longer moves its neighbours is still a spinner. |
1655
1812
  | `loadingLabel?` | `string` | — | Screen-reader-only label announced while `loading` is true. |
1656
1813
  | `lead?` | `Snippet` | — | Icon rendered BEFORE the label, outside it. Use this rather than putting
1657
1814
  an icon in `children`: the label then gets its own box, which is the only
@@ -1689,6 +1846,7 @@ snippet's text cannot be read before it renders.
1689
1846
  Only reaches the label when `lead`/`trail` give it its own box. Without
1690
1847
  one the label is an anonymous flex item that no selector can address,
1691
1848
  which is the whole reason those snippets exist. |
1849
+ | `topology?` | `'bounded' \| 'docked'` | `'bounded'` | `docked` joins this control to a neighbour and removes one or more curves. |
1692
1850
  | `children?` | `Snippet` | — | — |
1693
1851
  | `ref?` | `(forwarded)` | `null` | bindable |
1694
1852
 
@@ -1799,11 +1957,41 @@ Inherits all props from `ButtonBits`.
1799
1957
  |------|------|---------|-------|
1800
1958
  | `ref?` | `CardElement \| null` | `null` | bindable |
1801
1959
  | `tone?` | `'elevated' \| 'surface' \| 'well' \| 'flat'` | `'elevated'` | Surface tier the card paints on.
1802
- - `elevated` (default): the lifted surface. Use on the page background.
1803
- - `surface`: the base surface tier. Use when nesting cards inside another elevated container.
1804
- - `well`: recessed. For content the surrounding UI puts INTO the card a tool's
1805
- output pane, a transcript body — the container twin of a text field's fill.
1806
- - `flat`: no background; only the rounded/padded shell. For grouping content visually without adding contrast. |
1960
+ - `elevated` (default): the lifted surface, with a resting shadow. Use
1961
+ on the page background.
1962
+ - `surface`: the base surface tier, no shadow. Use when nesting cards
1963
+ inside another elevated container.
1964
+ - `well`: recessed. For content the surrounding UI puts INTO the card
1965
+ a tool's output pane, a transcript body — the container twin of a
1966
+ text field's fill.
1967
+ - `flat`: no background; only the rounded/padded shell. For grouping
1968
+ content visually without adding contrast.
1969
+ READ THE CLASSES AND THEY LOOK SWAPPED. They are not, and this is
1970
+ worth a sentence because a consumer read them, believed it, and
1971
+ shipped flat cards for months. `surface` paints `bg-well` and `well`
1972
+ paints `bg-field`, because era's utility names describe FILLS rather
1973
+ than tiers: `bg-well` is the surface's own resting fill (it is what a
1974
+ panel is painted with, and it pairs with `shadow-well` when you want
1975
+ recessed chrome), and `bg-field` is the recessed fill a text input
1976
+ sits in. The tone names are the tiers; the class names are the paints.
1977
+ A CARD THAT IS PRESSABLE SHOULD USUALLY BE `elevated`. Passing `href`
1978
+ or `onclick` turns on the interactive face — hover, focus, press — but
1979
+ it deliberately does NOT override the tier you asked for, so a
1980
+ `surface` link card does not get the raised tier's separation and can
1981
+ read flatter than the static cards around it. That combination was
1982
+ reported as a bug and it is a choice: a tone that silently changed
1983
+ under an event handler would be a worse one.
1984
+ HOW THE TIERS DIFFER IS SURFACE-DEPENDENT, so do not go looking for a
1985
+ fill difference on every surface. `base` mixes the elevated fill 6%
1986
+ toward `--color-bright` and adds a resting shadow, so there the tiers
1987
+ differ in COLOUR. `bevel` sets `--era-surface-bg-elevated` to
1988
+ `--era-surface-bg` outright — elevation is a chisel, not a tone, which
1989
+ is bevel's whole premise — so `surface` and `elevated` paint the
1990
+ IDENTICAL fill there and differ only in relief. A consumer measured
1991
+ exactly that on 30.8.0 bevel: both tones `oklch(0.195 0 none)` in dark,
1992
+ `0.863` in light, one variable, side by side. `glass` splits the tiers
1993
+ its own way again. So the tier is real on every surface; the thing
1994
+ carrying it is not always the fill. |
1807
1995
  | `header?` | `Snippet` | — | Rendered above the body with a subtle divider. |
1808
1996
  | `footer?` | `Snippet` | — | Rendered below the body with a subtle divider. |
1809
1997
  | `children?` | `Snippet` | — | — |
@@ -1832,6 +2020,22 @@ Inherits all props from `HTMLAttributes<HTMLDivElement>`.
1832
2020
  | `size?` | `'chip' \| 'pill'` | `'chip'` | `sm` (default) is the standalone free chip; `pill` is the smaller pill
1833
2021
  that nests inside an md container (a select trigger, a button). |
1834
2022
  | `ondismiss?` | `() => void` | — | — |
2023
+ | `dismissAs?` | `'button' \| 'span'` | `'button'` | What the dismiss control RENDERS AS. `button` (the default) is right
2024
+ for a free-standing chip. Use `span` when the chip lives inside
2025
+ another control.
2026
+ A <button> inside a <button> is not merely bad manners: it is invalid,
2027
+ and the HTML parser repairs it by CLOSING the outer control before the
2028
+ inner one. So the server's markup and the client's DOM disagree, Svelte
2029
+ discards the page's hydration and re-renders from scratch, and the
2030
+ chips briefly sit outside the trigger they belong to. era's own
2031
+ multi-select shipped exactly that (a Chip with `ondismiss` inside
2032
+ Select.Trigger) and logged node_invalid_placement_ssr on every load.
2033
+ `span` also drops the control from the accessibility tree rather than
2034
+ announcing a button inside a button — which is the honest description
2035
+ of what it is there: a pointer shortcut. The accessible way to remove
2036
+ a selection is the listbox itself, which is unaffected. Keyboard focus
2037
+ order is unchanged, because a nested button was never reachable in a
2038
+ sensible order anyway. |
1835
2039
  | `lead?` | `Snippet` | — | Icon rendered BEFORE the label, outside it. Use this rather than putting
1836
2040
  an icon in `children`: the label then gets its own box, which is the only
1837
2041
  place era's ink centring can reach. |
@@ -1997,6 +2201,24 @@ Inherits all props from `Separator.RootProps`.
1997
2201
  | Prop | Type | Default | Notes |
1998
2202
  |------|------|---------|-------|
1999
2203
  | `variant?` | `'line' \| 'slant'` | `'line'` | Use `slant` for inline dividers in breadcrumbs, titles, and compact metadata. |
2204
+ | `size?` | `keyof typeof SIZES` | — | Give a VERTICAL rule a height of its own, from the tier ladder, instead
2205
+ of stretching to whatever line it lands in. Ignored when horizontal.
2206
+ WHY YOU WOULD WANT IT. A stretched rule is a passenger: it has no
2207
+ intrinsic height, so it measures whatever its neighbours measure and
2208
+ contributes nothing back. That is right inside a Bar, whose height is
2209
+ already fixed — and it is why a rule in an auto-height row cannot hold
2210
+ that row's height steady. A consumer whose site header gained and lost
2211
+ a qualifier between pages watched the chrome resize, and pinned a
2212
+ `min-h-[calc(...)]` on the header to stop it. Sizing the rule removes
2213
+ that calc: it becomes the piece that stands the row up.
2214
+ (For the record, since the report named the wrong mechanism: a
2215
+ stretched rule does not GROW its row. Measured in an 18px auto-height
2216
+ row, adding one leaves the row at 18px and the rule resolves to 10px —
2217
+ the line minus its two gutter margins. It can only ever be shorter
2218
+ than the row it divides, which is the whole problem here.)
2219
+ A sized rule drops the gutter margins with the stretch: the height is
2220
+ now a deliberate number, and silently adding 2 x gutter to it would
2221
+ make it the one element on the page you cannot reason about. |
2000
2222
  | `ref?` | `(forwarded)` | `null` | bindable |
2001
2223
 
2002
2224
  ## Usage
@@ -3910,12 +4132,90 @@ Inherits all props from `Meter.RootProps`.
3910
4132
 
3911
4133
  | Prop | Type | Default | Notes |
3912
4134
  |------|------|---------|-------|
3913
- | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | The band this reading falls in. Tone is the fill's colour only — the
4135
+ | `tone?` | `'default' \| 'accent' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | The band this reading falls in. Tone is the fill's colour only — the
3914
4136
  track stays neutral, because the well belongs to the control and the
3915
4137
  fill belongs to the value.
3916
4138
  Tone carries no semantics on its own: a meter in the destructive band
3917
4139
  still needs an accessible label saying what has run out. Pair it with
3918
4140
  `aria-label` / a visible label, never rely on the hue alone. |
4141
+ | `threshold?` | `number` | — | A BOUNDARY INSIDE THE RANGE — in the same units as `value`, not a
4142
+ percentage. A quota with a soft limit before the hard one; a plan that
4143
+ includes 30 hours of the 50 it bills against; a temperature that is
4144
+ fine until it is not. The bar fills against `max`, and the mark says
4145
+ where the number the reader is actually deciding about sits.
4146
+ WHY THE MARK HAS NO COLOUR OF ITS OWN. It has to read against both
4147
+ sides of itself: over the empty track before the value reaches it, and
4148
+ over the fill afterwards. One token cannot do that — a colour tuned
4149
+ against the track disappears into the fill the moment the value
4150
+ crosses, which is exactly what a consumer hit hand-rolling this. So
4151
+ the mark is painted in the colour of the side it is NOT on: the fill's
4152
+ paint while the value is short of it, the track's fill once the value
4153
+ has passed. That contrast is not a new promise — it is the meter's
4154
+ own. A fill that did not stand out from its track would not be a
4155
+ meter, so borrowing the pair is the one choice that cannot be wrong in
4156
+ a theme, a tone, or a surface era has not seen.
4157
+ IT IS DECORATION, and silent on purpose. There is no `role` and
4158
+ nothing to announce: a screen reader gets `value`/`min`/`max` and the
4159
+ meter's own accessible name, and a boundary it cannot position adds
4160
+ nothing. If the threshold is part of the reading — it usually is —
4161
+ put the figure in `aria-label` and in a caption, where it is a number
4162
+ rather than a position.
4163
+ PASS `aria-valuetext` IF YOUR STORED UNIT IS NOT YOUR DISPLAYED ONE.
4164
+ `value`, `max` and `threshold` all have to sit in one unit for this
4165
+ arithmetic to work, and `aria-valuenow` is `value` verbatim — so a bar
4166
+ stored in minutes and read in hours announces "2640 of 3000", a figure
4167
+ the customer was never quoted. Converting the props is not available
4168
+ to you (it would move the mark), which makes `aria-valuetext` the only
4169
+ lever; it passes through `restProps` onto the same element. Reported
4170
+ by a consumer billing minutes and quoting hours.
4171
+ Outside `[min, max]` it renders NOTHING rather than clamping to an
4172
+ end, because a mark pinned to the edge of the bar would claim a
4173
+ boundary that is not in the range. |
4174
+ | `buffer?` | `number` | — | A REGION FROM `min` THAT IS A DIFFERENT KIND OF QUANTITY — painted
4175
+ under the fill, over the track, in the same units as `value`.
4176
+ The media player's buffered-progress shape, and the name is borrowed
4177
+ from it, but the reading is more general: some of the range is already
4178
+ accounted for. A plan whose first 30 of 50 hours are included in the
4179
+ monthly minimum is not "24 hours into a bill", it is "24 of 30 hours
4180
+ you have already paid for" — money spent against money that will be
4181
+ spent, which is a distinction the bar could not draw.
4182
+ NO COLOUR OF ITS OWN, for the reason `threshold` has none: it has to
4183
+ be legible over the track while never competing with the fill that
4184
+ crosses it, and a token tuned for one of those is wrong for the other.
4185
+ It is the fill's own paint at 30%, so it cannot be wrong in a theme,
4186
+ a tone or a surface era has not seen, and it is the same 30% Slider's
4187
+ `progress` has used for a seek bar's buffer since it shipped.
4188
+ UNDER the fill, so it disappears as the value consumes it. That is the
4189
+ reading, not a limitation: once you are past your included hours, the
4190
+ included region IS spent, and `threshold` is what goes on marking
4191
+ where it ended.
4192
+ CLAMPED to the range rather than dropped, which is where it differs
4193
+ from `threshold`. A region can be honestly truncated — a buffer past
4194
+ `max` means the whole visible bar is accounted for, and painting it
4195
+ all says exactly that. A point cannot: a mark pinned to the edge would
4196
+ claim a boundary that is not in the range, so an out-of-range
4197
+ `threshold` renders nothing instead.
4198
+ Silent, like the mark. `aria-valuetext` already carries the reading,
4199
+ and a second region a screen reader cannot position adds nothing.
4200
+ (Slider spells its equivalent `progress` and takes a 0–1 FRACTION.
4201
+ Here every positional prop is in the value's own units — `value`,
4202
+ `max`, `threshold`, `buffer` — because a component that mixed the two
4203
+ would be a bug generator. The names differ because on a Meter
4204
+ `progress` would be a synonym for `value`.) |
4205
+ | `start?` | `Snippet` | — | Captions under the two ends of the bar — what the range MEANS.
4206
+ A meter states a proportion and leaves the reader to work out what of.
4207
+ "0m used" on the left and "50h before approval" on the right is not
4208
+ decoration, it is the reading; every consumer of a bare meter ends up
4209
+ hand-rolling the same flex row underneath it, which is where this came
4210
+ from. `start` is left, `end` is right, either may stand alone.
4211
+ NOT ANNOUNCED. They are captions, so a screen reader gets the meter's
4212
+ value and its own `aria-label`, not these. If the range needs saying
4213
+ out loud, say it in the label too — the same rule `tone` carries.
4214
+ THE DOM IS UNCHANGED WITHOUT THEM. With neither snippet this renders
4215
+ exactly the element it always did, so `class` still lands on the track
4216
+ and the geometry audits still walk the same box. Passing one wraps the
4217
+ track in a column, and `class` still lands on the track. |
4218
+ | `end?` | `Snippet` | — | — |
3919
4219
  | `ref?` | `(forwarded)` | `null` | bindable |
3920
4220
 
3921
4221
  <!-- end: meter -->