Single source of truth for typography values that the recipe layer
consumes. The foundation aliases `--font-ui` and `--leading-ui` (read
by ~30 recipe tokens in `lib/recipes/base.ts`) now derive from the
canonical `label` named style instead of carrying duplicate literals:
--style-label-font-family: var(--font-family-primary);
--style-label-line-height: 1.25;
--font-ui: var(--style-label-font-family, var(--font-family-primary));
--leading-ui: var(--style-label-line-height, 1.25);
Chain: typography.ts styles → --style-{name}-* → --leading-ui / --font-ui
→ recipe tokens → component CSS. Editing
`STATIC_TYPOGRAPHY.styles.label.lineHeight` now propagates to every
recipe in one go.
Why not push recipes to consume `--style-{name}-*` directly:
- t-shirt sizes (xs/sm/md/lg/xl) don't map to four semantic buckets
- per-component matices (description/caption/hint) need their own
color / weight / letter-spacing
- ref libraries (Radix Themes, Mantine, MUI, Chakra) all keep
numerical scale for component internals; semantic layer is only for
user-facing typography primitives (`<Text variant="body2">`)
Audit rule R-2.7 (warn): detects literal font-size / font-weight /
line-height / letter-spacing in eidos component CSS. Escape valves:
var(...), numeric identities (0/0px/1), keywords (inherit/initial/
unset), or trailing `/* literal: <reason> */` comment. Current run
flags 6 components with letter-spacing/font-size literals (all
intentional micro-tracking and em-relative; can be annotated case by
case).
Documentation:
- src/uix/eidos/README.md § "Vertebración tipográfica" — two-layer
architecture rationale, alias chain diagram, comparison vs Radix
Themes / Chakra / Mantine / MUI, escape valves
- web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md § 4.11 — pointer to
R-2.7 + cross-link to the foundation doc
Verified end-to-end in browser at /uix/components/field:
--font-ui → 'Instrument Sans', system-ui, sans-serif
--style-label-font-family → 'Instrument Sans', system-ui, sans-serif
--leading-ui → 1.25
--style-label-line-height → 1.25
computed [data-field-label].line-height → 17.5px (= 14 × 1.25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Literal typography (${literalTypoOffenses.length}): ${literalTypoOffenses.slice(0,5).join('; ')}${literalTypoOffenses.length>5?'...':''}. Use a recipe token or named-style var, or add a /* literal: <reason> */ comment.`
| **Foundation numerical** (`--font-size-*`, `--font-family-primary`, …) | recipe tokens en `lib/recipes/base.ts` + foundation aliases (`--font-ui`, `--leading-ui`, …) | Internals de componentes (Field labels, Combobox triggers, Button text, …) — necesitan **escalado t-shirt** (`xs/sm/md/lg/xl`) que NO mapea limpio a una semántica fija. |
| **Named styles** (`--style-label-*`, `--style-body-*`, `--style-caption-*`, `--style-h{1..6}-*`, `--style-{hero,prose,code}-*`) | typography primitives (`<Text>`, `<Heading>`, `<Display>`, `<Code>`, `<Link>`, …) | API de usuario para componer contenido — el USUARIO eligió "label" o "body" y quiere que ese rol semántico se respete. |
**Las dos capas no son redundantes**: sirven a contextos distintos. La
numérica vertebra el _interior_ del sistema; la semántica vertebra la
_superficie_ que el consumer compone.
### Cómo se vertebran sin duplicarse — la chain de aliases
Donde un valor coincide entre las dos capas, **el foundation alias lee
del named style, no al revés**. Single source of truth: el named style.