@signal9/era-ui 4.16.9 → 6.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.
@@ -39,7 +39,7 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
39
39
  - `era-text-trim` — vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge. Fixed-height, non-editable elements ONLY: never on an <input>/<textarea>, and never on the same element as `truncate` or any overflow clip — put it on the fixed-height ROW and let the truncating child centre inside it. See era-text-trim-caps for ALL-CAPS.
40
40
  - `truncate` — single-line horizontal truncation with an ellipsis — put it on the truncating CHILD inside a trimmed or leading-none row, never on the same element as era-text-trim; it will not clip ascenders or descenders.
41
41
  - `era-text-trim-caps` — the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never for mixed-case text (a label like "System" reads high; use era-text-trim), and never on the same element as `truncate`.
42
- - `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.
42
+ - `era-token-ink · era-ink-center` — the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).
43
43
  - `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.
44
44
  - `era-collapse` — the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
45
45
  - `h-xs · h-xxs · h-sm · h-md · h-lg` — the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
@@ -148,7 +148,7 @@ enough to stay var-form — `min-w-(--era-h-md)` etc. remain the escape hatch.
148
148
  | `era-text-trim` | vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge. Fixed-height, non-editable elements ONLY: never on an <input>/<textarea>, and never on the same element as `truncate` or any overflow clip — put it on the fixed-height ROW and let the truncating child centre inside it. See era-text-trim-caps for ALL-CAPS. |
149
149
  | `truncate` | single-line horizontal truncation with an ellipsis — put it on the truncating CHILD inside a trimmed or leading-none row, never on the same element as era-text-trim; it will not clip ascenders or descenders. |
150
150
  | `era-text-trim-caps` | the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never for mixed-case text (a label like "System" reads high; use era-text-trim), and never on the same element as `truncate`. |
151
- | `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. |
151
+ | `era-token-ink` `era-ink-center` | the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies). |
152
152
  | `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. |
153
153
  | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
154
154
  | `h-xs` `h-xxs` `h-sm` `h-md` `h-lg` | the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*). |
@@ -340,39 +340,48 @@ Declared in `index.css`.
340
340
  }
341
341
  ```
342
342
 
343
- ## era-ink-center
343
+ ## era-token-ink · era-ink-center
344
344
 
345
- **Use it for:** on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field never alongside era-text-trim.
345
+ **Use it for:** the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).
346
346
 
347
- The EDITABLE counterpart of era-text-trim for native <input>s, which the
348
- trim is forbidden on (it collapses the UA's inner editor box and slices the
349
- glyphs; see typography/collapsed-text-trim).
347
+ TOKEN INK the ink centring for a pill whose content is a TOKEN: an id, a
348
+ count, a status, a tag. Font-independent, unlike everything it replaces.
350
349
 
351
- Without help, a tier-height input centres its text by FONT BOX, and the ui
352
- stacks' font box is deeply asymmetric: ascent ≈1.07em against descent
353
- ≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em
354
- LOW so a field's text sat visibly below the trimmed label beside it.
350
+ THE BUG IT FIXES, which had been shipping library-wide and invisibly:
351
+ `text-box-trim` DOES NOTHING ON A FLEX CONTAINER. Era's controls are all
352
+ flex, and a flex container's bare text becomes an ANONYMOUS flex item — a box
353
+ no selector can reach and no non-inherited property can touch. So
354
+ `era-text-trim` on a Badge, a Chip, a Button or a Bar contributed exactly one
355
+ thing: `line-height: 1`. The ink was then centred by `align-items: center`
356
+ on the LINE BOX, which is precisely the font-metric-dependent centring the
357
+ trim exists to eliminate. Measured on the shipped mono stack: a badge reading
358
+ "AC-2.1" sat 2px high in an 18px pill, and no amount of tuning the trim's
359
+ BAND changed it, because the trim was never running.
355
360
 
356
- The value is OPTICAL, not the derivation's: pure x-band centring computes
357
- to ~0.23em of padding, but pixel-measured against real Title-Case text it
358
- over-lifts the eye weighs the cap-to-baseline mass, which x-band
359
- centring parks ~2px high (caps 2.75px off the top against 5.7px of air
360
- under the baseline, measured at dense). 0.08em splits the difference
361
- between the x-band ideal and the cap-band ideal, landing caps ~3.7px in an
362
- 18px field with ~4.7px under the baseline — the reading where neither
363
- band's partisans win and neither complains.
361
+ The mechanism here restores it: a BLOCK box (where text-box-trim does apply)
362
+ whose trimmed line box is then centred by `align-content` block-container
363
+ alignment, so no flex formatting context and no anonymous item. The browser
364
+ computes the band from the font's own metrics, which is what makes it hold
365
+ across ui-monospace, system-ui and ui-serif instead of needing a constant
366
+ per stack.
364
367
 
365
- The lift is padding, not a translateY: an icon-less Input paints the field
366
- chrome itself, and a transform would drag the field along with the ink.
367
- padding-bottom shrinks the content box from below, and a single-line input
368
- re-centres its line box in what remains the ink rises by exactly half the
369
- padding, the chrome doesn't move, and the caret and selection follow the
370
- text. In em on purpose: the correction is a fraction of the type, so it
371
- holds across every density and text size with one value.
368
+ CAP band, not the x-height band the general trim uses, because the content
369
+ class is different: a token is caps and digits (both cap-height), and
370
+ centring the x-band parks them high by (cap x)/2. Measured on the shipped
371
+ stacks, cap-band centring takes an id/count/status from −2.0px to 0.0.
372
+ Prose and sentence-case labels keep era-text-trim; this is for tokens.
372
373
 
373
374
  Declared in `index.css`.
374
375
 
375
376
  ```css
377
+ @utility era-token-ink {
378
+ display: block;
379
+ align-content: center;
380
+ text-align: center;
381
+ line-height: 1;
382
+ text-box: trim-both cap alphabetic;
383
+ }
384
+
376
385
  @utility era-ink-center {
377
386
  padding-bottom: 0.08em;
378
387
  }
@@ -14,6 +14,8 @@ A container with a BOUNDED tier height (bar, row, trigger, pill) takes geometric
14
14
 
15
15
  **3. The even-gap law.** Inside any container, `(container_height − child_height) / 2` must equal the gap on ALL four sides. A `h-xxs` control in a `h-md` bar sits on a 3px wall at dense — so the bar's padding AND the gap between its children are that same value. `gap-gutter` / `p-gutter` is the standard inter-element gap (sp/2); the concentric insets (`p-inset-*`) are the text-symmetric ones.
16
16
 
17
+ The same law in its simplest form, for containers with no tier: **a box pads its content equally at top and bottom.** Horizontal asymmetry is often deliberate (a bar's leading side sits at the text inset while its trailing side keeps the concentric wall); vertical asymmetry is a bug — nothing should sit closer to its top edge than its bottom, and era audits for it.
18
+
17
19
  **4. Radius follows the corner REGION's tier, not the box's size.**
18
20
  `rounded-xs · xxs · sm · md · lg` walk the same ladder as the heights, and the ladder is built so that `rd_outer − rd_inner` equals the concentric gap — nested corners then share a centre of curvature automatically. A big panel whose corner is formed by an `h-md` bar takes `rounded-md`, not `rounded-lg`. Never hardcode a radius: `data-corners="square"` collapses the tokens to 0 and a literal value would survive it.
19
21
 
@@ -28,6 +30,8 @@ Text that WRAPS is the opposite regime: it needs leading, so it carries `leading
28
30
 
29
31
  **7. Colour comes from semantic tokens, not the ramp.** `text-fg` (controls/labels) · `text-bright` (emphasis, headings) · `text-muted` (body copy) · `text-destructive` / `-success` / `-warning`. Fills: `bg-hover` for a latched ROW, `bg-fill` for a standing pill, `bg-highlight` for transient hover. These compose across themes; a raw scale step does not.
30
32
 
33
+ **Nesting a pill in a card.** A pill at a card's corner is NOT governed by the concentric identity — that law needs a height term and a card has no tier. The card pads optically, the pill keeps its own tier radius; do not hand-tune padding to chase the corner. If a container genuinely IS tier-height chrome (a bar, a row, a trigger) then it is bounded and the concentric rule applies in full.
34
+
31
35
  **Verifying your own output.** era ships a runtime audit that measures these laws against rendered DOM — tier conformance, even gaps, concentric radii, chopped glyphs, wrapping text at control leading. If you are generating markup, render it and run the audit rather than eyeballing: it is the same instrument the library's own test suite asserts against.
32
36
 
33
37
  Full derivations: `{{ORIGIN}}/spacing.md` (the ladder and every token), `{{ORIGIN}}/text.md` (type and trim), `{{ORIGIN}}/utilities.md` (every class, what it emits, when to use it).
@@ -73,7 +77,7 @@ Classes, not components — they need no import beyond the stylesheet. Reach for
73
77
  - `era-text-trim` — vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge. Fixed-height, non-editable elements ONLY: never on an <input>/<textarea>, and never on the same element as `truncate` or any overflow clip — put it on the fixed-height ROW and let the truncating child centre inside it. See era-text-trim-caps for ALL-CAPS.
74
78
  - `truncate` — single-line horizontal truncation with an ellipsis — put it on the truncating CHILD inside a trimmed or leading-none row, never on the same element as era-text-trim; it will not clip ascenders or descenders.
75
79
  - `era-text-trim-caps` — the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never for mixed-case text (a label like "System" reads high; use era-text-trim), and never on the same element as `truncate`.
76
- - `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.
80
+ - `era-token-ink · era-ink-center` — the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).
77
81
  - `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.
78
82
  - `era-collapse` — the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).
79
83
  - `h-xs · h-xxs · h-sm · h-md · h-lg` — the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*).
@@ -44,7 +44,7 @@
44
44
  "slug": "utilities",
45
45
  "title": "Utilities",
46
46
  "summary": "The CSS classes era ships alongside the components — links, ink-centred text, shimmer, hidden scrollbars.",
47
- "tokenEstimate": 6097,
47
+ "tokenEstimate": 6197,
48
48
  "sections": [
49
49
  "Overview",
50
50
  "era-interactive",
@@ -53,7 +53,7 @@
53
53
  "era-text-trim",
54
54
  "truncate",
55
55
  "era-text-trim-caps",
56
- "era-ink-center",
56
+ "era-token-ink · era-ink-center",
57
57
  "era-shimmer",
58
58
  "era-collapse",
59
59
  "h-xs · h-xxs · h-sm · h-md · h-lg",
@@ -1023,14 +1023,15 @@
1023
1023
  "css": "@utility era-text-trim-caps {\n\tline-height: 1;\n\ttext-box: trim-both cap alphabetic;\n}"
1024
1024
  },
1025
1025
  {
1026
- "name": "era-ink-center",
1026
+ "name": "era-token-ink",
1027
1027
  "names": [
1028
+ "era-token-ink",
1028
1029
  "era-ink-center"
1029
1030
  ],
1030
1031
  "file": "index.css",
1031
- "useCase": "on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field never alongside era-text-trim.",
1032
- "description": "The EDITABLE counterpart of era-text-trim for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baselinethe reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.",
1033
- "css": "@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
1032
+ "useCase": "the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).",
1033
+ "description": "TOKEN INK the ink centring for a pill whose content is a TOKEN: an id, a\ncount, a status, a tag. Font-independent, unlike everything it replaces.\n\nTHE BUG IT FIXES, which had been shipping library-wide and invisibly:\n`text-box-trim` DOES NOTHING ON A FLEX CONTAINER. Era's controls are all\nflex, and a flex container's bare text becomes an ANONYMOUS flex item a box\nno selector can reach and no non-inherited property can touch. So\n`era-text-trim` on a Badge, a Chip, a Button or a Bar contributed exactly one\nthing: `line-height: 1`. The ink was then centred by `align-items: center`\non the LINE BOX, which is precisely the font-metric-dependent centring the\ntrim exists to eliminate. Measured on the shipped mono stack: a badge reading\n\"AC-2.1\" sat 2px high in an 18px pill, and no amount of tuning the trim's\nBAND changed it, because the trim was never running.\n\nThe mechanism here restores it: a BLOCK box (where text-box-trim does apply)\nwhose trimmed line box is then centred by `align-content` block-container\nalignment, so no flex formatting context and no anonymous item. The browser\ncomputes the band from the font's own metrics, which is what makes it hold\nacross ui-monospace, system-ui and ui-serif instead of needing a constant\nper stack.\n\nCAP band, not the x-height band the general trim uses, because the content\nclass is different: a token is caps and digits (both cap-height), and\ncentring the x-band parks them high by (cap x)/2. Measured on the shipped\nstacks, cap-band centring takes an id/count/status from −2.0px to 0.0.\nProse and sentence-case labels keep era-text-trim; this is for tokens.",
1034
+ "css": "@utility era-token-ink {\n\tdisplay: block;\n\talign-content: center;\n\ttext-align: center;\n\tline-height: 1;\n\ttext-box: trim-both cap alphabetic;\n}\n\n@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
1034
1035
  },
1035
1036
  {
1036
1037
  "name": "era-shimmer",
@@ -61,14 +61,15 @@
61
61
  "css": "@utility era-text-trim-caps {\n\tline-height: 1;\n\ttext-box: trim-both cap alphabetic;\n}"
62
62
  },
63
63
  {
64
- "name": "era-ink-center",
64
+ "name": "era-token-ink",
65
65
  "names": [
66
+ "era-token-ink",
66
67
  "era-ink-center"
67
68
  ],
68
69
  "file": "index.css",
69
- "useCase": "on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field never alongside era-text-trim.",
70
- "description": "The EDITABLE counterpart of era-text-trim for native <input>s, which the\ntrim is forbidden on (it collapses the UA's inner editor box and slices the\nglyphs; see typography/collapsed-text-trim).\n\nWithout help, a tier-height input centres its text by FONT BOX, and the ui\nstacks' font box is deeply asymmetric: ascent ≈1.07em against descent\n≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em\nLOW so a field's text sat visibly below the trimmed label beside it.\n\nThe value is OPTICAL, not the derivation's: pure x-band centring computes\nto ~0.23em of padding, but pixel-measured against real Title-Case text it\nover-lifts the eye weighs the cap-to-baseline mass, which x-band\ncentring parks ~2px high (caps 2.75px off the top against 5.7px of air\nunder the baseline, measured at dense). 0.08em splits the difference\nbetween the x-band ideal and the cap-band ideal, landing caps ~3.7px in an\n18px field with ~4.7px under the baselinethe reading where neither\nband's partisans win and neither complains.\n\nThe lift is padding, not a translateY: an icon-less Input paints the field\nchrome itself, and a transform would drag the field along with the ink.\npadding-bottom shrinks the content box from below, and a single-line input\nre-centres its line box in what remains the ink rises by exactly half the\npadding, the chrome doesn't move, and the caret and selection follow the\ntext. In em on purpose: the correction is a fraction of the type, so it\nholds across every density and text size with one value.",
71
- "css": "@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
70
+ "useCase": "the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).",
71
+ "description": "TOKEN INK the ink centring for a pill whose content is a TOKEN: an id, a\ncount, a status, a tag. Font-independent, unlike everything it replaces.\n\nTHE BUG IT FIXES, which had been shipping library-wide and invisibly:\n`text-box-trim` DOES NOTHING ON A FLEX CONTAINER. Era's controls are all\nflex, and a flex container's bare text becomes an ANONYMOUS flex item a box\nno selector can reach and no non-inherited property can touch. So\n`era-text-trim` on a Badge, a Chip, a Button or a Bar contributed exactly one\nthing: `line-height: 1`. The ink was then centred by `align-items: center`\non the LINE BOX, which is precisely the font-metric-dependent centring the\ntrim exists to eliminate. Measured on the shipped mono stack: a badge reading\n\"AC-2.1\" sat 2px high in an 18px pill, and no amount of tuning the trim's\nBAND changed it, because the trim was never running.\n\nThe mechanism here restores it: a BLOCK box (where text-box-trim does apply)\nwhose trimmed line box is then centred by `align-content` block-container\nalignment, so no flex formatting context and no anonymous item. The browser\ncomputes the band from the font's own metrics, which is what makes it hold\nacross ui-monospace, system-ui and ui-serif instead of needing a constant\nper stack.\n\nCAP band, not the x-height band the general trim uses, because the content\nclass is different: a token is caps and digits (both cap-height), and\ncentring the x-band parks them high by (cap x)/2. Measured on the shipped\nstacks, cap-band centring takes an id/count/status from −2.0px to 0.0.\nProse and sentence-case labels keep era-text-trim; this is for tokens.",
72
+ "css": "@utility era-token-ink {\n\tdisplay: block;\n\talign-content: center;\n\ttext-align: center;\n\tline-height: 1;\n\ttext-box: trim-both cap alphabetic;\n}\n\n@utility era-ink-center {\n\tpadding-bottom: 0.08em;\n}"
72
73
  },
73
74
  {
74
75
  "name": "era-shimmer",
@@ -44,7 +44,7 @@ enough to stay var-form — `min-w-(--era-h-md)` etc. remain the escape hatch.
44
44
  | `era-text-trim` | vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge. Fixed-height, non-editable elements ONLY: never on an <input>/<textarea>, and never on the same element as `truncate` or any overflow clip — put it on the fixed-height ROW and let the truncating child centre inside it. See era-text-trim-caps for ALL-CAPS. |
45
45
  | `truncate` | single-line horizontal truncation with an ellipsis — put it on the truncating CHILD inside a trimmed or leading-none row, never on the same element as era-text-trim; it will not clip ascenders or descenders. |
46
46
  | `era-text-trim-caps` | the era-text-trim variant for ALL-CAPS single-line strings (kbd hints, acronym badges) — never for mixed-case text (a label like "System" reads high; use era-text-trim), and never on the same element as `truncate`. |
47
- | `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. |
47
+ | `era-token-ink` `era-ink-center` | the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies). |
48
48
  | `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. |
49
49
  | `era-collapse` | the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden). |
50
50
  | `h-xs` `h-xxs` `h-sm` `h-md` `h-lg` | the tier-height ladder — h-xs (icon) · h-xxs (nested pill) · h-sm (standalone pill) · h-md (control) · h-lg (bar); the named form of h-(--era-h-*). |
@@ -236,39 +236,48 @@ Declared in `index.css`.
236
236
  }
237
237
  ```
238
238
 
239
- ## era-ink-center
240
-
241
- **Use it for:** on a native single-line <input> (or the text layer that must mirror one) inside a fixed-height field never alongside era-text-trim.
242
-
243
- The EDITABLE counterpart of era-text-trim for native <input>s, which the
244
- trim is forbidden on (it collapses the UA's inner editor box and slices the
245
- glyphs; see typography/collapsed-text-trim).
246
-
247
- Without help, a tier-height input centres its text by FONT BOX, and the ui
248
- stacks' font box is deeply asymmetric: ascent ≈1.07em against descent
249
- ≈0.29em. Centring that box parks the visible band (caps, x-height) ~0.115em
250
- LOW so a field's text sat visibly below the trimmed label beside it.
251
-
252
- The value is OPTICAL, not the derivation's: pure x-band centring computes
253
- to ~0.23em of padding, but pixel-measured against real Title-Case text it
254
- over-lifts the eye weighs the cap-to-baseline mass, which x-band
255
- centring parks ~2px high (caps 2.75px off the top against 5.7px of air
256
- under the baseline, measured at dense). 0.08em splits the difference
257
- between the x-band ideal and the cap-band ideal, landing caps ~3.7px in an
258
- 18px field with ~4.7px under the baseline the reading where neither
259
- band's partisans win and neither complains.
260
-
261
- The lift is padding, not a translateY: an icon-less Input paints the field
262
- chrome itself, and a transform would drag the field along with the ink.
263
- padding-bottom shrinks the content box from below, and a single-line input
264
- re-centres its line box in what remains — the ink rises by exactly half the
265
- padding, the chrome doesn't move, and the caret and selection follow the
266
- text. In em on purpose: the correction is a fraction of the type, so it
267
- holds across every density and text size with one value.
239
+ ## era-token-ink · era-ink-center
240
+
241
+ **Use it for:** the ink centring for a token pill a badge, a KV value, any short caps-or-digits label in a fixed-height box; it needs no icon sibling (with one the box must stay flex, and the general trim applies).
242
+
243
+ TOKEN INK the ink centring for a pill whose content is a TOKEN: an id, a
244
+ count, a status, a tag. Font-independent, unlike everything it replaces.
245
+
246
+ THE BUG IT FIXES, which had been shipping library-wide and invisibly:
247
+ `text-box-trim` DOES NOTHING ON A FLEX CONTAINER. Era's controls are all
248
+ flex, and a flex container's bare text becomes an ANONYMOUS flex item a box
249
+ no selector can reach and no non-inherited property can touch. So
250
+ `era-text-trim` on a Badge, a Chip, a Button or a Bar contributed exactly one
251
+ thing: `line-height: 1`. The ink was then centred by `align-items: center`
252
+ on the LINE BOX, which is precisely the font-metric-dependent centring the
253
+ trim exists to eliminate. Measured on the shipped mono stack: a badge reading
254
+ "AC-2.1" sat 2px high in an 18px pill, and no amount of tuning the trim's
255
+ BAND changed it, because the trim was never running.
256
+
257
+ The mechanism here restores it: a BLOCK box (where text-box-trim does apply)
258
+ whose trimmed line box is then centred by `align-content` block-container
259
+ alignment, so no flex formatting context and no anonymous item. The browser
260
+ computes the band from the font's own metrics, which is what makes it hold
261
+ across ui-monospace, system-ui and ui-serif instead of needing a constant
262
+ per stack.
263
+
264
+ CAP band, not the x-height band the general trim uses, because the content
265
+ class is different: a token is caps and digits (both cap-height), and
266
+ centring the x-band parks them high by (cap x)/2. Measured on the shipped
267
+ stacks, cap-band centring takes an id/count/status from −2.0px to 0.0.
268
+ Prose and sentence-case labels keep era-text-trim; this is for tokens.
268
269
 
269
270
  Declared in `index.css`.
270
271
 
271
272
  ```css
273
+ @utility era-token-ink {
274
+ display: block;
275
+ align-content: center;
276
+ text-align: center;
277
+ line-height: 1;
278
+ text-box: trim-both cap alphabetic;
279
+ }
280
+
272
281
  @utility era-ink-center {
273
282
  padding-bottom: 0.08em;
274
283
  }
@@ -261,6 +261,44 @@
261
261
  *
262
262
  * @use on a native single-line <input> (or the text layer that must mirror
263
263
  * one) inside a fixed-height field — never alongside era-text-trim. */
264
+ /* TOKEN INK — the ink centring for a pill whose content is a TOKEN: an id, a
265
+ * count, a status, a tag. Font-independent, unlike everything it replaces.
266
+ *
267
+ * THE BUG IT FIXES, which had been shipping library-wide and invisibly:
268
+ * `text-box-trim` DOES NOTHING ON A FLEX CONTAINER. Era's controls are all
269
+ * flex, and a flex container's bare text becomes an ANONYMOUS flex item — a box
270
+ * no selector can reach and no non-inherited property can touch. So
271
+ * `era-text-trim` on a Badge, a Chip, a Button or a Bar contributed exactly one
272
+ * thing: `line-height: 1`. The ink was then centred by `align-items: center`
273
+ * on the LINE BOX, which is precisely the font-metric-dependent centring the
274
+ * trim exists to eliminate. Measured on the shipped mono stack: a badge reading
275
+ * "AC-2.1" sat 2px high in an 18px pill, and no amount of tuning the trim's
276
+ * BAND changed it, because the trim was never running.
277
+ *
278
+ * The mechanism here restores it: a BLOCK box (where text-box-trim does apply)
279
+ * whose trimmed line box is then centred by `align-content` — block-container
280
+ * alignment, so no flex formatting context and no anonymous item. The browser
281
+ * computes the band from the font's own metrics, which is what makes it hold
282
+ * across ui-monospace, system-ui and ui-serif instead of needing a constant
283
+ * per stack.
284
+ *
285
+ * CAP band, not the x-height band the general trim uses, because the content
286
+ * class is different: a token is caps and digits (both cap-height), and
287
+ * centring the x-band parks them high by (cap − x)/2. Measured on the shipped
288
+ * stacks, cap-band centring takes an id/count/status from −2.0px to 0.0.
289
+ * Prose and sentence-case labels keep era-text-trim; this is for tokens.
290
+ *
291
+ * @use the ink centring for a token pill — a badge, a KV value, any short
292
+ * caps-or-digits label in a fixed-height box; it needs no icon sibling (with
293
+ * one the box must stay flex, and the general trim applies). */
294
+ @utility era-token-ink {
295
+ display: block;
296
+ align-content: center;
297
+ text-align: center;
298
+ line-height: 1;
299
+ text-box: trim-both cap alphabetic;
300
+ }
301
+
264
302
  @utility era-ink-center {
265
303
  padding-bottom: 0.08em;
266
304
  }
@@ -12,27 +12,44 @@
12
12
  * absolute root size (e.g. `html { font-size: 16px }`); see the docs' layout.css.
13
13
  */
14
14
 
15
+ /*
16
+ * THE SHIPPED STACKS — named, and read directly.
17
+ *
18
+ * These rules used to say `var(--font-mono, <era's stack>)`, treating era's
19
+ * list as a fallback. It never once applied. Tailwind v4 ALWAYS defines
20
+ * --font-mono in its own base theme, so the fallback arm was unreachable and
21
+ * every consumer silently rendered TAILWIND's font stack — while era's docs,
22
+ * which pin --font-mono to generic `monospace` for taste, rendered a third
23
+ * thing. Three different fonts: one the library believed it shipped, one
24
+ * consumers actually got, one the maintainer verified against. Font metrics
25
+ * drive ink centring, so every alignment constant tuned in the docs was
26
+ * calibrated against a font nobody else renders — which is why the same
27
+ * "text isn't vertically centred" bug kept coming back from consumers and
28
+ * kept looking fine here.
29
+ *
30
+ * The axis now reads era's OWN namespaced token, which nothing upstream
31
+ * defines. A consumer (or these docs) re-points a stack by setting
32
+ * --era-font-mono / -sans / -serif; nothing can do it by accident.
33
+ */
34
+ :root {
35
+ --era-font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
36
+ --era-font-sans: system-ui, sans-serif;
37
+ --era-font-serif: ui-serif, Georgia, 'Times New Roman', serif;
38
+ }
39
+
15
40
  [data-font='mono'],
16
41
  [data-font='mono'] * {
17
- font-family: var(
18
- --font-mono,
19
- ui-monospace,
20
- 'SF Mono',
21
- Menlo,
22
- Consolas,
23
- 'Liberation Mono',
24
- monospace
25
- ) !important;
42
+ font-family: var(--era-font-mono) !important;
26
43
  }
27
44
 
28
45
  [data-font='sans'],
29
46
  [data-font='sans'] * {
30
- font-family: var(--font-sans, system-ui, sans-serif) !important;
47
+ font-family: var(--era-font-sans) !important;
31
48
  }
32
49
 
33
50
  [data-font='serif'],
34
51
  [data-font='serif'] * {
35
- font-family: var(--font-serif, ui-serif, Georgia, 'Times New Roman', serif) !important;
52
+ font-family: var(--era-font-serif) !important;
36
53
  }
37
54
 
38
55
  /*
@@ -11,7 +11,15 @@
11
11
  // out from under the ink and the label bled past the fill. A pill that must
12
12
  // fit a tight row does it the Tool.Chip way: the BOX holds and an inner
13
13
  // min-w-0 span truncates. Enforced at runtime by layout/pill-holds-its-ink.
14
- base: 'inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim',
14
+ // not-has-[svg]: the ink mechanism switches with the content SHAPE.
15
+ //
16
+ // A badge holding only a token (the overwhelming case — an id, a count, a
17
+ // status) becomes a BLOCK box so text-box-trim can actually run; see
18
+ // era-token-ink for why the trim is inert on a flex container and what
19
+ // that cost. A badge holding an icon must stay flex to lay the icon out
20
+ // beside the text, and keeps the general trim — its line-height:1 plus
21
+ // flex centring, which is the best available when the text is anonymous.
22
+ base: 'inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim not-has-[svg]:era-token-ink',
15
23
  variants: {
16
24
  tone: {
17
25
  default: 'bg-fill text-fg',
@@ -6,7 +6,7 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
6
6
  success: "bg-success text-success-fg";
7
7
  warning: "bg-warning text-warning-fg";
8
8
  };
9
- }, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim", {
9
+ }, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim not-has-[svg]:era-token-ink", {
10
10
  tone: {
11
11
  default: "bg-fill text-fg";
12
12
  accent: "bg-primary text-primary-fg";
@@ -19,7 +19,19 @@
19
19
  // outer padding — each section pads itself so dividers run edge-to-edge.
20
20
  // `p-0` (not just "no class") so a <button> card drops the browser's
21
21
  // default button padding and lines up with the <div>/<a> variants.
22
- sectioned: { true: 'p-0', false: 'px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)' },
22
+ //
23
+ // EVEN on all four sides. This carried a half-height top padding
24
+ // (pt-gutter against pb-pad-md) borrowed from `p-content`, whose
25
+ // asymmetry is earned: a content panel always follows a section heading
26
+ // ROW, and that row's own bar already contributes slack above the text,
27
+ // so padding evenly on top of it spends the space twice. A card has no
28
+ // row above it — its own top edge IS the boundary — so the borrowed
29
+ // rule had nothing to subtract and simply made every card top-heavy
30
+ // (4px above the content against 8px below it, at dense). A consumer
31
+ // reported it as "cards aren't evenly spaced", which is exactly what it
32
+ // was: the even-gap law, broken by default, in the component that
33
+ // bounds more consumer content than any other.
34
+ sectioned: { true: 'p-0', false: 'p-(--era-pad-md)' },
23
35
  interactive: {
24
36
  true: 'cursor-pointer hover:bg-(--era-highlight) hover:shadow-(--era-highlight-shadow) focus-visible:bg-(--era-highlight) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
25
37
  }
@@ -101,17 +113,15 @@
101
113
  {#snippet body()}
102
114
  {#if sectioned}
103
115
  {#if header}
104
- <div
105
- class="border-b border-divider-faded px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md) text-bright"
106
- >
116
+ <div class="border-b border-divider-faded p-(--era-pad-md) text-bright">
107
117
  {@render header()}
108
118
  </div>
109
119
  {/if}
110
- <div class="px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)">
120
+ <div class="p-(--era-pad-md)">
111
121
  {@render children?.()}
112
122
  </div>
113
123
  {#if footer}
114
- <div class="border-t border-divider-faded px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)">
124
+ <div class="border-t border-divider-faded p-(--era-pad-md)">
115
125
  {@render footer()}
116
126
  </div>
117
127
  {/if}
@@ -7,7 +7,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
7
7
  };
8
8
  sectioned: {
9
9
  true: "p-0";
10
- false: "px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)";
10
+ false: "p-(--era-pad-md)";
11
11
  };
12
12
  interactive: {
13
13
  true: "cursor-pointer hover:bg-(--era-highlight) hover:shadow-(--era-highlight-shadow) focus-visible:bg-(--era-highlight) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
@@ -21,7 +21,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
21
21
  };
22
22
  sectioned: {
23
23
  true: "p-0";
24
- false: "px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)";
24
+ false: "p-(--era-pad-md)";
25
25
  };
26
26
  interactive: {
27
27
  true: "cursor-pointer hover:bg-(--era-highlight) hover:shadow-(--era-highlight-shadow) focus-visible:bg-(--era-highlight) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
@@ -35,7 +35,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
35
35
  };
36
36
  sectioned: {
37
37
  true: "p-0";
38
- false: "px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md)";
38
+ false: "p-(--era-pad-md)";
39
39
  };
40
40
  interactive: {
41
41
  true: "cursor-pointer hover:bg-(--era-highlight) hover:shadow-(--era-highlight-shadow) focus-visible:bg-(--era-highlight) era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
@@ -30,7 +30,12 @@
30
30
  {...restProps}
31
31
  >
32
32
  <Badge {tone} class="h-full rounded-none border-r border-divider py-0">{key}</Badge>
33
- <span class="min-w-(--era-h-xxs) px-(--era-inset-sm) text-center text-muted tabular-nums"
33
+ <!-- self-stretch + era-token-ink: a KV value is a token (a number, an id, a
34
+ status), and the block mechanism is the only one where text-box-trim
35
+ actually runs — see era-token-ink. Stretching gives that block a full-
36
+ height box for align-content to centre the trimmed line in. -->
37
+ <span
38
+ class="era-token-ink min-w-(--era-h-xxs) self-stretch px-(--era-inset-sm) text-muted tabular-nums"
34
39
  >{value}</span
35
40
  >
36
41
  </span>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "4.16.9",
3
+ "version": "6.0.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -119,6 +119,7 @@
119
119
  "@types/node": "^24.13.3",
120
120
  "@types/three": "^0.184.1",
121
121
  "@xyflow/svelte": "^1.6.2",
122
+ "conventional-changelog-conventionalcommits": "^10.2.1",
122
123
  "eslint": "^9.39.5",
123
124
  "eslint-config-prettier": "^10.1.8",
124
125
  "eslint-plugin-better-tailwindcss": "^4.7.0",