@signal9/era-ui 3.0.1 → 3.2.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.
@@ -169,10 +169,70 @@
169
169
  }
170
170
  }
171
171
 
172
- /* Register color tokens as Tailwind theme values for autocomplete and bare utility usage */
172
+ /* ------------------------------------------------------------------ */
173
+ /* THE PUBLIC CLASS API */
174
+ /* */
175
+ /* Era's consumer-facing styling surface is NAMED utilities — a user */
176
+ /* of the library writes `h-md rounded-md px-md shadow`, never */
177
+ /* `h-(--era-h-md)`-style var classes. Where Tailwind already has a */
178
+ /* name for the concept (rounded-md, shadow, ease) era OVERLOADS it */
179
+ /* so stock-looking markup renders on-design; where it doesn't, era */
180
+ /* registers its own (h-md, px-md, gap-gutter, p-card). */
181
+ /* */
182
+ /* The --era-* custom properties remain the internal contract (and */
183
+ /* the escape hatch for anything without a name — e.g. min-w-* tier */
184
+ /* names are NOT registered because Tailwind's container scale */
185
+ /* already owns min-w-md/lg; use min-w-(--era-h-md) there). */
186
+ /* */
187
+ /* Everything here is a thin name over a token, so the full axis */
188
+ /* system — density, surface, corners, motion — flows through */
189
+ /* untouched. @theme inline is load-bearing: it inlines the var() */
190
+ /* reference instead of snapshotting a value, so each USE re-resolves */
191
+ /* against the element's own inherited axes. */
192
+ /* ------------------------------------------------------------------ */
173
193
  @theme inline {
174
194
  /* Density-derived body size: `text-body` replaces text-[length:var(--era-text)] */
175
195
  --text-body: var(--era-text);
196
+ /* Radius ladder — Tailwind's own rounded-* names now walk era's concentric
197
+ * ladder (rd-T = rd-xs + (h-T − h-xs)/2), ride the corners toggle, and
198
+ * collapse to 0 on square. rounded-item is the menu-row radius
199
+ * (rd-md − panel-inset) that keeps rows concentric inside a rounded-md
200
+ * panel on every surface. */
201
+ --radius-xs: var(--era-rd-xs);
202
+ --radius-xxs: var(--era-rd-xxs);
203
+ --radius-sm: var(--era-rd-sm);
204
+ --radius-md: var(--era-rd-md);
205
+ --radius-lg: var(--era-rd-lg);
206
+ --radius-item: var(--era-item-rd);
207
+ /* Surface chrome — bare `shadow` is the resting control edge, shadow-lg the
208
+ * floating panel, shadow-well the recessed container, shadow-pressed the
209
+ * active/latched face, shadow-highlight the material half of hover feedback.
210
+ * Flat renders them as none, bevel as chisels, glass as frosted rims — the
211
+ * class stays the same. */
212
+ --shadow: var(--era-shadow);
213
+ /* v4 marks bare `shadow` a deprecated spelling of shadow-sm, and lints
214
+ * auto-fix one to the other — alias both to the control edge so either
215
+ * spelling renders era chrome. */
216
+ --shadow-sm: var(--era-shadow);
217
+ --shadow-lg: var(--era-shadow-lg);
218
+ --shadow-well: var(--era-shadow-well);
219
+ --shadow-pressed: var(--era-shadow-pressed);
220
+ --shadow-highlight: var(--era-highlight-shadow);
221
+ /* Surface fills — bg-well is the surface's own resting fill (pairs with
222
+ * shadow-well for recessed chrome), bg-elevated the raised tier (pairs with
223
+ * shadow-lg + glass-blur for floating chrome), bg-highlight the transient
224
+ * hover/focus/keyboard wash (pairs with shadow-highlight; latched states
225
+ * stay on opaque bg-hover), bg-overlay the modal scrim. */
226
+ --color-well: var(--era-surface-bg);
227
+ --color-elevated: var(--era-surface-bg-elevated);
228
+ --color-highlight: var(--era-highlight);
229
+ --color-overlay: var(--era-overlay-bg);
230
+ /* Motion — bare transition utilities default to the motion axis, so
231
+ * `transition-colors` alone is on-design (0s at instant, longer at extra).
232
+ * ease-base/duration-base name the axis explicitly for overrides. */
233
+ --default-transition-duration: var(--era-duration);
234
+ --default-transition-timing-function: var(--era-ease);
235
+ --ease-base: var(--era-ease);
176
236
  /* Surface divider colors: border-divider, bg-divider-faded, … */
177
237
  --color-divider: var(--era-divider-color);
178
238
  --color-divider-faded: var(--era-divider-faded);
@@ -206,3 +266,176 @@
206
266
  --color-warning: var(--color-warning);
207
267
  --color-warning-fg: var(--color-warning-fg);
208
268
  }
269
+
270
+ /* ------------------------------------------------------------------ */
271
+ /* Named tier + spacing utilities */
272
+ /* */
273
+ /* Static utilities (not @theme) because Tailwind's spacing namespace */
274
+ /* is shared across h/w/p/m/gap — registering `md` there would give */
275
+ /* px-md and h-md the SAME value, and era's tiers and insets are */
276
+ /* different token families. Each name below maps to exactly one */
277
+ /* token. Kept terse; the ladder itself is documented in density.css */
278
+ /* and on the /spacing page. */
279
+ /* ------------------------------------------------------------------ */
280
+
281
+ /* @use the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm
282
+ * (standalone pill) · h-md (control) · h-lg (bar); the named form of
283
+ * h-(--era-h-*). */
284
+ @utility h-xs {
285
+ height: var(--era-h-xs);
286
+ }
287
+ @utility h-xxs {
288
+ height: var(--era-h-xxs);
289
+ }
290
+ @utility h-sm {
291
+ height: var(--era-h-sm);
292
+ }
293
+ @utility h-md {
294
+ height: var(--era-h-md);
295
+ }
296
+ @utility h-lg {
297
+ height: var(--era-h-lg);
298
+ }
299
+
300
+ /* @use minimum tier height for containers that may grow — a bar that is at
301
+ * least lg-tier but can wrap (min-h-lg). */
302
+ @utility min-h-xs {
303
+ min-height: var(--era-h-xs);
304
+ }
305
+ @utility min-h-xxs {
306
+ min-height: var(--era-h-xxs);
307
+ }
308
+ @utility min-h-sm {
309
+ min-height: var(--era-h-sm);
310
+ }
311
+ @utility min-h-md {
312
+ min-height: var(--era-h-md);
313
+ }
314
+ @utility min-h-lg {
315
+ min-height: var(--era-h-lg);
316
+ }
317
+
318
+ /* @use a tier-sized square — size-xs is THE icon size (a Lucide glyph beside
319
+ * body text), size-md a square control face. */
320
+ @utility size-xs {
321
+ width: var(--era-h-xs);
322
+ height: var(--era-h-xs);
323
+ }
324
+ @utility size-xxs {
325
+ width: var(--era-h-xxs);
326
+ height: var(--era-h-xxs);
327
+ }
328
+ @utility size-sm {
329
+ width: var(--era-h-sm);
330
+ height: var(--era-h-sm);
331
+ }
332
+ @utility size-md {
333
+ width: var(--era-h-md);
334
+ height: var(--era-h-md);
335
+ }
336
+ @utility size-lg {
337
+ width: var(--era-h-lg);
338
+ height: var(--era-h-lg);
339
+ }
340
+
341
+ /* @use tier-to-tier nesting padding (sp/2) — the horizontal padding of a bar
342
+ * or row that holds tiered controls; the named form of px-(--era-px-*). */
343
+ @utility px-sm {
344
+ padding-inline: var(--era-px-sm);
345
+ }
346
+ @utility px-md {
347
+ padding-inline: var(--era-px-md);
348
+ }
349
+ @utility px-lg {
350
+ padding-inline: var(--era-px-lg);
351
+ }
352
+
353
+ /* @use the inter-element gap (sp/2) — flex/grid gap between sibling controls,
354
+ * and the matching gutter padding a scrollable list or rail owes its rows on
355
+ * all four sides (p-gutter on the rail = gap-gutter between its rows). */
356
+ @utility gap-gutter {
357
+ gap: var(--era-gap);
358
+ }
359
+ @utility p-gutter {
360
+ padding: var(--era-gap);
361
+ }
362
+ @utility px-gutter {
363
+ padding-inline: var(--era-gap);
364
+ }
365
+ @utility py-gutter {
366
+ padding-block: var(--era-gap);
367
+ }
368
+
369
+ /* @use text-symmetric content padding — (tier − text)/2, the inset that
370
+ * vertically centers body text in its tier; px-inset-md is a text button's
371
+ * side padding, p-inset-md a code block's. */
372
+ @utility p-inset-xxs {
373
+ padding: var(--era-inset-xxs);
374
+ }
375
+ @utility p-inset-sm {
376
+ padding: var(--era-inset-sm);
377
+ }
378
+ @utility p-inset-md {
379
+ padding: var(--era-inset-md);
380
+ }
381
+ @utility px-inset-xxs {
382
+ padding-inline: var(--era-inset-xxs);
383
+ }
384
+ @utility px-inset-sm {
385
+ padding-inline: var(--era-inset-sm);
386
+ }
387
+ @utility px-inset-md {
388
+ padding-inline: var(--era-inset-md);
389
+ }
390
+ @utility py-inset-xxs {
391
+ padding-block: var(--era-inset-xxs);
392
+ }
393
+ @utility py-inset-sm {
394
+ padding-block: var(--era-inset-sm);
395
+ }
396
+ @utility py-inset-md {
397
+ padding-block: var(--era-inset-md);
398
+ }
399
+
400
+ /* @use pill side padding — the wider-than-vertical horizontal breathing room
401
+ * badge/chip text wants (glyph-advance derived, ~2× the vertical inset). */
402
+ @utility px-pill-xxs {
403
+ padding-inline: var(--era-pill-xxs);
404
+ }
405
+ @utility px-pill-sm {
406
+ padding-inline: var(--era-pill-sm);
407
+ }
408
+
409
+ /* @use text-field side padding — where typed text starts in an md-tier input;
410
+ * the named form of px-(--era-field-px). */
411
+ @utility px-field {
412
+ padding-inline: var(--era-field-px);
413
+ }
414
+
415
+ /* @use optical padding for unbounded containers — p-panel for compact shells
416
+ * and menu-adjacent chrome, p-card for cards/dialogs/popovers, p-content for
417
+ * full content panels. Grows super-linearly with density so whitespace holds
418
+ * its perceived share as controls loosen. */
419
+ @utility p-panel {
420
+ padding: var(--era-pad-sm);
421
+ }
422
+ @utility p-card {
423
+ padding: var(--era-pad-md);
424
+ }
425
+ @utility p-content {
426
+ padding: var(--era-content-p);
427
+ }
428
+
429
+ /* @use menu-panel padding — the surface-owned inset (0 on flat, 2px on
430
+ * glass/bevel) that keeps row highlights inside a floating panel's frame;
431
+ * pairs with rounded-item on the rows. */
432
+ @utility p-menu {
433
+ padding: var(--era-panel-inset);
434
+ }
435
+
436
+ /* @use the motion axis as an explicit transition-duration — for the rare case
437
+ * a utility (not the transition-* default) needs to name it; 0s at
438
+ * data-motion="instant", longer at "extra". */
439
+ @utility duration-base {
440
+ transition-duration: var(--era-duration);
441
+ }
@@ -29,6 +29,6 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
29
29
  onDragEnd?: (data: DragEventData) => void;
30
30
  children?: Snippet;
31
31
  };
32
- declare const PaneRoot: import("svelte").Component<$$ComponentProps, {}, "position" | "size" | "ref">;
32
+ declare const PaneRoot: import("svelte").Component<$$ComponentProps, {}, "size" | "position" | "ref">;
33
33
  type PaneRoot = ReturnType<typeof PaneRoot>;
34
34
  export default PaneRoot;
@@ -35,6 +35,6 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
35
35
  header?: Snippet;
36
36
  children?: Snippet;
37
37
  };
38
- declare const Pane: Component<$$ComponentProps, {}, "position" | "size" | "ref">;
38
+ declare const Pane: Component<$$ComponentProps, {}, "size" | "position" | "ref">;
39
39
  type Pane = ReturnType<typeof Pane>;
40
40
  export default Pane;
@@ -4,6 +4,6 @@ import { type ButtonVariants } from '../button';
4
4
  type $$ComponentProps = WithoutChildrenOrChild<Toggle.RootProps> & Omit<ButtonVariants, 'active'> & {
5
5
  children?: Snippet;
6
6
  };
7
- declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "ref" | "pressed">;
7
+ declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "pressed" | "ref">;
8
8
  type Toggle = ReturnType<typeof Toggle>;
9
9
  export default Toggle;
@@ -4,11 +4,31 @@ import { createTV } from 'tailwind-variants';
4
4
  // glass-blur is a custom @utility (styles/surfaces/glass.css) that sets backdrop-filter,
5
5
  // so it must conflict-resolve against the backdrop-blur group. text-body is
6
6
  // a registered font-size token — without this it would be misread as a color.
7
+ // The rest teaches tailwind-merge era's named class API (styles/index.css):
8
+ // values it can't know about (h-md, px-inset-md, gap-gutter, shadow-well, …)
9
+ // must land in their real class groups, or a consumer's `class="h-lg"` would
10
+ // fail to replace a component's h-(--era-h-md) and both would apply.
11
+ // Colors (bg-elevated, bg-highlight, …) need no entry — the bg group already
12
+ // accepts any name. rounded-xs/sm/md/lg and bare shadow/shadow-lg are stock
13
+ // names tailwind-merge knows; only era's additions to those scales are listed.
14
+ const TIERS = ['xs', 'xxs', 'sm', 'md', 'lg'];
15
+ const INSETS = ['inset-xxs', 'inset-sm', 'inset-md'];
7
16
  const twMergeConfig = {
8
17
  extend: {
9
18
  classGroups: {
10
19
  'backdrop-blur': ['glass-blur'],
11
- 'font-size': [{ text: ['body'] }]
20
+ 'font-size': [{ text: ['body'] }],
21
+ h: [{ h: TIERS }],
22
+ 'min-h': [{ 'min-h': TIERS }],
23
+ size: [{ size: TIERS }],
24
+ p: [{ p: ['gutter', 'panel', 'card', 'content', 'menu', ...INSETS] }],
25
+ px: [{ px: ['sm', 'md', 'lg', 'gutter', 'field', 'pill-xxs', 'pill-sm', ...INSETS] }],
26
+ py: [{ py: ['gutter', ...INSETS] }],
27
+ gap: [{ gap: ['gutter'] }],
28
+ rounded: [{ rounded: ['xxs', 'item'] }],
29
+ shadow: [{ shadow: ['well', 'pressed', 'highlight'] }],
30
+ ease: [{ ease: ['base'] }],
31
+ duration: [{ duration: ['base'] }]
12
32
  }
13
33
  }
14
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
package/skill/SKILL.md CHANGED
@@ -4,20 +4,31 @@ description: |
4
4
  Design-system methodology for building UIs with @sig-nine/era-ui (Svelte 5 +
5
5
  Tailwind v4). Use when: writing or reviewing any component, layout, or page
6
6
  in a project that uses era-ui — sizing controls, spacing/padding, radii,
7
- surfaces, motion, or composing library components. Covers: the sp-derived
8
- tier ladder, the even-gap and concentricity laws, geometric vs optical
9
- padding, surface/shadow rules, motion tokens, and the mistakes agents make.
10
- version: 1.0.0
7
+ surfaces, motion, or composing library components. Covers: the named class
8
+ API, the sp-derived tier ladder, the even-gap and concentricity laws,
9
+ geometric vs optical padding, surface/shadow rules, motion, and the
10
+ mistakes agents make.
11
+ version: 1.1.0
11
12
  ---
12
13
 
13
14
  # Building with era-ui
14
15
 
15
16
  Every size, gap, and radius derives from ONE number per density mode: `sp`
16
17
  (8/10/12/16 for dense/balanced/spacious/touch). Never write a hardcoded px,
17
- rem, radius, duration, or color — there is a token for everything, and the
18
+ rem, radius, duration, or color — there is a class for everything, and the
18
19
  whole system re-derives per element, so any `data-*` axis override on any
19
20
  subtree just works.
20
21
 
22
+ **Write named classes, not var classes.** Era's public styling API is pure
23
+ Tailwind-looking utilities: `h-md rounded-md px-md shadow`, never
24
+ `h-(--era-h-md)`-style var spellings. Where Tailwind already names the concept
25
+ era overloads it (`rounded-md` walks era's ladder, bare `shadow` is the
26
+ surface chrome, `transition-colors` rides the motion axis); where it doesn't,
27
+ era registers its own (`h-md`, `gap-gutter`, `p-card`). The `--era-*` custom
28
+ properties remain public as the escape hatch for the few things without a
29
+ name — noted inline below — but reaching for one when a named class exists is
30
+ a review finding.
31
+
21
32
  ## The five axes (data attributes, all orthogonal)
22
33
 
23
34
  | Axis | Attribute | Values |
@@ -34,72 +45,80 @@ to ~13px, silently shrinking every rem-derived token).
34
45
 
35
46
  ## The tier ladder
36
47
 
37
- | Tier | Height token | Radius token | Use for |
38
- | ----- | ------------- | -------------- | ----------------------------------------------- |
39
- | `xs` | `--era-h-xs` | `--era-rd-xs` | icons (THE icon size), switch thumbs |
40
- | `xxs` | `--era-h-xxs` | `--era-rd-xxs` | pills nested inside md (badge/chip in a button) |
41
- | `sm` | `--era-h-sm` | `--era-rd-sm` | standalone pills, switch tracks, KV |
42
- | `md` | `--era-h-md` | `--era-rd-md` | buttons, inputs, selects, triggers |
43
- | `lg` | `--era-h-lg` | `--era-rd-lg` | bars, containers, dialog headers |
48
+ | Tier | Height | Square | Radius | Use for |
49
+ | ----- | ------- | ---------- | ------------- | ----------------------------------------------- |
50
+ | `xs` | `h-xs` | `size-xs` | `rounded-xs` | icons (THE icon size), switch thumbs |
51
+ | `xxs` | `h-xxs` | `size-xxs` | `rounded-xxs` | pills nested inside md (badge/chip in a button) |
52
+ | `sm` | `h-sm` | `size-sm` | `rounded-sm` | standalone pills, switch tracks, KV |
53
+ | `md` | `h-md` | `size-md` | `rounded-md` | buttons, inputs, selects, triggers |
54
+ | `lg` | `h-lg` | `size-lg` | `rounded-lg` | bars, containers, dialog headers |
44
55
 
45
- Usage: `h-(--era-h-md)`, `rounded-(--era-rd-md)`, icons `size-(--era-h-xs)`.
46
- Always the paren shorthand — never `h-[var(--era-h-md)]`.
56
+ `min-h-*` exists for containers that may grow. One escape hatch: the
57
+ single-glyph square floor is `min-w-(--era-h-md)` — `min-w-md` is Tailwind's
58
+ container scale (28rem), so era does not shadow it.
47
59
 
48
60
  ## The two laws
49
61
 
50
62
  1. **Even gaps** — the padding between a container's edge and its children
51
- must equal the gap between the children, on every side. `--era-gap`
52
- (= sp/2) is the universal inter-element gap.
63
+ must equal the gap between the children, on every side. `gap-gutter`
64
+ (= sp/2) is the universal inter-element gap, and `p-gutter` (+ `px-`/`py-`)
65
+ is the matching gutter a scrollable list or rail owes its rows.
53
66
  2. **Concentricity** — `rd-T − rd-U = (h-T − h-U)/2` for every tier pair. So:
54
67
  give EVERY element the radius of its OWN height tier, center children with
55
- the matching concentric-inset token, and nested corners share a center of
56
- curvature automatically. The inset tokens ARE those gaps:
57
- `--era-xs-inset-sm` (xs inside sm, = sp/2), `--era-xxs-inset-md`,
68
+ the matching concentric inset, and nested corners share a center of
69
+ curvature automatically. The concentric insets are advanced enough to stay
70
+ token-form: `--era-xs-inset-sm` (xs inside sm, = sp/2), `--era-xxs-inset-md`,
58
71
  `--era-xs-inset-md`, `--era-xs-inset-lg`, `--era-sm-inset-lg`.
59
72
 
60
73
  **The trap**: a container's radius follows its corner REGION's tier, never its
61
74
  overall bulk. A large panel whose corners are formed by an sm-tier handle bar
62
- is `rd-sm`, however big the panel. Choosing radius by visual importance is the
63
- #1 way concentricity breaks.
75
+ is `rounded-sm`, however big the panel. Choosing radius by visual importance
76
+ is the #1 way concentricity breaks.
64
77
 
65
78
  ## Padding: geometric vs optical
66
79
 
67
80
  - **Bounded (tier-height) containers** — bars, triggers, pills: geometric.
68
- Side padding `--era-px-*` (sp/2); text-symmetric vertical centering
69
- `--era-inset-{xxs,sm,md}` = (h-T − text)/2; pill side breathing
70
- `--era-pill-{xxs,sm}` = (h-T − 0.6·text)/2 (text wants ~2× more horizontal
71
- than vertical padding).
72
- - **Unbounded containers** — cards, dialogs, popover bodies, content panels:
73
- optical. `--era-pad-sm` / `--era-pad-md` / `--era-pad-lg` (super-linear
81
+ Side padding `px-sm`/`px-md`/`px-lg` (sp/2); text-symmetric vertical
82
+ centering `p`/`px`/`py-inset-{xxs,sm,md}` = (h-T − text)/2; pill side
83
+ breathing `px-pill-{xxs,sm}` = (h-T − 0.6·text)/2 (text wants ~2× more
84
+ horizontal than vertical padding); text-field side padding `px-field`.
85
+ - **Unbounded containers** — optical: `p-panel` (compact shells), `p-card`
86
+ (cards, dialogs, popovers), `p-content` (full content panels). Super-linear
74
87
  across modes so whitespace keeps pace with growing controls while text
75
- stays 14px). Never pad a card with `px-*` or ad-hoc `calc`.
88
+ stays 14px. Never pad a card with `px-*` or ad-hoc `calc`.
76
89
 
77
90
  ## Surfaces and shadows
78
91
 
79
- - Depth is **shadow, not border**: raised chrome takes `shadow-(--era-shadow)`;
80
- recessed content containers (inputs, tracks, wells) take
81
- `shadow-(--era-shadow-well)`; pressed/latched faces take
82
- `shadow-(--era-shadow-pressed)`; on press, bevel sinks the CONTENT via `--era-press-sink-x`/`-y` while the box holds still.
92
+ - Depth is **shadow, not border**: raised chrome takes bare `shadow`;
93
+ floating panels take `shadow-lg`; recessed content containers (inputs,
94
+ tracks, wells) take `shadow-well`; pressed/latched faces take
95
+ `shadow-pressed`. On press, bevel sinks the CONTENT via
96
+ `--era-press-sink-x`/`-y` while the box holds still.
83
97
  - **Transient vs latched fills**: hover/focus/highlighted feedback uses
84
- `bg-(--era-highlight)` (surface-owned; translucent on glass). Held state
85
- (toggle on, current tab, active button) uses opaque `bg-hover`. Never swap.
86
- - Panels: background `bg-(--era-surface-bg)` / elevated
87
- `bg-(--era-surface-bg-elevated)`, plus `glass-blur` on floating panels.
98
+ `bg-highlight` + `shadow-highlight` (surface-owned; translucent on glass).
99
+ Held state (toggle on, current tab, active button) uses opaque `bg-hover`.
100
+ Never swap.
101
+ - Panels: resting fill `bg-well` (pairs with `shadow-well`), raised fill
102
+ `bg-elevated` (pairs with `shadow-lg` + `glass-blur` on floating panels),
103
+ modal scrim `bg-overlay`. Menu panels pad with `p-menu` and their rows take
104
+ `rounded-item` so the row highlight stays concentric inside the frame.
88
105
 
89
106
  ## Motion
90
107
 
91
- Only `duration-(--era-duration)` and `ease-(--era-ease)`never `duration-150`
92
- or a literal bezier. `instant` collapses everything to 0s (also forced by
93
- reduced-motion); `extra` adds a gentle overshoot. Deriving compound timings
94
- (shimmer loops, cursor pulses) as multiples of `--era-duration` makes them
95
- motion-axis-aware for free.
108
+ The bare `transition-*` utilities default to the motion axis — `transition-colors`
109
+ alone is on-design: 0s at `instant` (also forced by reduced-motion), longer at
110
+ `extra` with a gentle overshoot. Name the axis explicitly with `ease-base` /
111
+ `duration-base` when overriding; never `duration-150` or a literal bezier.
112
+ Deriving compound timings (shimmer loops, cursor pulses) as multiples of
113
+ `--era-duration` makes them motion-axis-aware for free.
96
114
 
97
115
  ## Text
98
116
 
99
117
  - `text-body` for control/body text (density-aware size); `text-fg` default,
100
118
  `text-bright` emphasis, `text-muted` secondary.
101
- - Single-glyph elements carry `min-w` equal to their tier height (a lone
102
- character renders square, never a skinny pill).
119
+ - Single-glyph elements carry a min-width equal to their tier height (a lone
120
+ character renders square, never a skinny pill) — `min-w-(--era-h-md)`, see
121
+ the ladder note.
103
122
  - Single-line control text next to icons carries `era-text-trim`
104
123
  (`line-height: 1` + `text-box: trim-both ex alphabetic`) — flexbox centers
105
124
  the line box, not the ink; the utility centers the x-height band and holds
@@ -119,8 +138,9 @@ motion-axis-aware for free.
119
138
 
120
139
  Not everything era exposes is a component — these ride along with the stylesheet
121
140
  and need no import. Reach for one before hand-rolling the same styling or bending
122
- a component into the role. Full reference: `/utilities.md`; machine-readable list:
123
- `/utilities.json` (or `@sig-nine/era-ui/utilities.json`).
141
+ a component into the role. Full reference (including every named scale above):
142
+ `/utilities.md`; machine-readable list: `/utilities.json` (or
143
+ `@sig-nine/era-ui/utilities.json`).
124
144
 
125
145
  | Class | Use it for |
126
146
  | -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
@@ -142,7 +162,9 @@ a component into the role. Full reference: `/utilities.md`; machine-readable lis
142
162
  ```
143
163
 
144
164
  - `ui` primitives are minimal bits-ui wrappers — all bits props/snippets pass
145
- through; style via `class` (merged with tailwind-merge, consumer wins).
165
+ through; style via `class` (merged with tailwind-merge, consumer wins — the
166
+ named classes conflict-resolve against internals, so `class="h-lg"` on an
167
+ md control just works).
146
168
  - `OS` (or `@sig-nine/era-ui/os`): WindowManager + workspaces, Desktop,
147
169
  Window, Taskbar (built-in command bar). `AI` (or `/ai`): Conversation,
148
170
  Message, Response, PromptInput, Tool/Reasoning/Task over the Step primitive.
@@ -151,10 +173,12 @@ a component into the role. Full reference: `/utilities.md`; machine-readable lis
151
173
 
152
174
  ## Review checklist (the mistakes to catch)
153
175
 
154
- 1. Hardcoded px/rem/radius/duration/color → replace with a token.
176
+ 1. Hardcoded px/rem/radius/duration/color → replace with the named class.
155
177
  2. Radius chosen by bulk instead of corner region.
156
178
  3. Unequal edge padding vs inner gap at any nesting level.
157
- 4. `px-*`/geometric padding on an unbounded container (or `pad-*` on a pill).
158
- 5. `bg-hover` used for hover feedback, or `--era-highlight` for latched state.
159
- 6. Icon not `size-(--era-h-xs)`; single glyph without the min-w square floor.
160
- 7. Arbitrary-value form `x-[var(--era-y)]` instead of `x-(--era-y)`.
179
+ 4. `px-*`/geometric padding on an unbounded container (or `p-card` on a pill).
180
+ 5. `bg-hover` used for hover feedback, or `bg-highlight` for latched state.
181
+ 6. Icon not `size-xs`; single glyph without the min-w square floor.
182
+ 7. A `-(--era-*)` var class where a named class exists (`h-(--era-h-md)` →
183
+ `h-md`); if the var form is genuinely needed, always the paren shorthand —
184
+ never `x-[var(--era-y)]`.