From a9111572cd8f97edb423c66728166819e820ede6 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 17 Jun 2026 14:41:33 +0200 Subject: [PATCH] feat(eidos): universal 1:1 control-text scaling + icon font-coupling Supersede the "compact control md=14" archetypes (Fase 7) with a single rule: control text scales 1:1 with its size (md=16) across all components, so changing --font-size-md re-themes the whole system. Applied to button, badge, breadcrumb, calendar, pagination, radio-group, toolbar, file-upload, tag-group, stepper, toggle, tooltip (the field family was already 1:1). Button + search-field icons now follow the font 1:1 (icon ~= font+2; density-independent, like the type scale). Fix button icon vertical centering: the [data-button-icon] slot didn't flex-center, so the inline sat on the text baseline ~1px below the button center. Exceptions left as-is (not control text): avatar (initials sized to the circle diameter), accordion (section-heading prose scale), password-field icon-size (it is the visibility-trigger button box, control-coupled by design), radio-cards (icon follows the card title). THEMING.md section 5 rewritten: 1:1 universal mapping + "density is orthogonal to typography" (density scales space + control-height only; font + icon ride only the global --scaling zoom). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/uix/eidos/THEMING.md | 67 +++++++----- src/uix/eidos/components/button/button.css | 6 ++ src/uix/eidos/generated/base.css | 103 +++++++++---------- src/uix/eidos/lib/recipes/base.ts | 113 ++++++++++----------- 4 files changed, 152 insertions(+), 137 deletions(-) diff --git a/src/uix/eidos/THEMING.md b/src/uix/eidos/THEMING.md index ba7500bf3..73a8a2817 100644 --- a/src/uix/eidos/THEMING.md +++ b/src/uix/eidos/THEMING.md @@ -512,7 +512,7 @@ Cada size canónico genera tokens coordinados: ``` --size-md-control-height: 36px ---size-md-font-size: 14px /* = --font-size-sm (control compacto), NO 16px */ +--size-md-font-size: 14px /* bundle HUÉRFANO — recipes ya NO lo consumen; canon vivo = 1:1 (md=16) */ --size-md-font-line-height: 1.45 --size-md-icon-size: 18px /* = --icon-size-md */ --size-md-padding-inline: 12px @@ -557,22 +557,32 @@ recipe soporta. Categorías: La categorización vive en [`web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md §12.8`](../../../web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md). -### Arquetipos size→fuente (Fase 7 — 2026-06-15) +### Mapeo size→fuente: 1:1 universal (override 2026-06-17) -El bundle `--size-*` (arriba) es la **referencia del arquetipo `control`**: empareja -`md`-control con fuente `sm` (14px) e icono `md`. Pero los componentes NO consumen el -bundle directamente — cada recipe declara su `font-size-{size}` apuntando a `--font-size-*` -(la auditoría encontró que esa indirección, hand-rolled, derivó en varias convenciones). -Las **convenciones canónicas** (cada size → token de fuente) son: +> **Supersede los arquetipos `control`/`compact`/`dense` de la Fase 7.** El usuario +> decidió que un framework de grado empresarial necesita **una sola fuente de verdad**: +> el texto de control sigue la escala tipográfica **1:1** — `font-size-{size}` = +> `var(--font-size-{size})` — en TODOS los componentes. Así, cambiar `--font-size-md` +> a 15px re-adapta el tema entero sin tocar un solo recipe. El `md`-control es **16px** +> (ya NO 14: la doctrina "control compacto md=14" queda revocada). -| Arquetipo | sm | md | lg | xl | Componentes | -|---|---|---|---|---|---| -| **control** | sm | sm | md | lg | button, toggle\*, switch | -| **compact** | xs | sm | md | lg | badge, breadcrumb, select, editable, file-upload, stepper, tooltip | -| **dense** | xs | sm | sm | md | field, form, calendar, pagination, spin/date/time/color-field, radio-group | +**Escala** (xs/sm/md/lg/xl/xxl): **12 · 14 · 16 · 18→20 · 24→28 · 32→48** +(`lib/primitives/typography.ts`; lg/xl/xxl fluidas `clamp()`). + +Recipes llevados a 1:1 (2026-06-17): button, badge, breadcrumb, calendar, pagination, +radio-group, toolbar, file-upload, tag-group, stepper, toggle, tooltip — más toda la +familia de campos (field, spin/date/time/color-field, search/password-field, select, +editable, tags-input), ya 1:1 desde el sprint de campos. + +**Excepciones legítimas** (NO son texto-de-control → no aplican 1:1): +- **avatar/marker** — la fuente es la inicial dentro del círculo, escalada al **diámetro** + (24→96px): glifo proporcional, no control. +- **accordion** — el trigger es un encabezado de sección: usa la escala de **prosa** + `--text-N-size` (xs→text-2 … full→text-6), progresión propia coherente. +- **words / palabras / chronos** — tracks WIP excluidos. -(\* toggle/avatar son una variante extra-compacta — un escalón más abajo.) Todas -comparten `md→sm` (14px, decisión 2 = paridad Radix/Material). +El bundle `--size-*` (arriba) sigue **huérfano** (0 consumidores); su +`--size-md-font-size: 14px` ya no refleja la regla — el canon vivo es el 1:1 de los recipes. **Regla dura (guard de coherencia)** — `recipe-css-contract.test.ts`: > Ningún token `font-size-*` / `icon-size-*` de recipe puede ser un **literal px/rem** @@ -586,15 +596,13 @@ re-declarar el mapeo) queda como follow-up; el guard es lo que **impide la deriv ### Campos: input 1:1 + label un paso por debajo (override 2026-06-17) -La familia de **campos** (input + label) NO sigue el arquetipo `dense`/`compact` de -arriba — su mapeo colapsado (sm=md=14, sin escalado real de xs a lg) se rechazó. La -**regla canónica de campo** es: +La familia de **campos** aplica el **1:1 universal** (arriba) al **input**, y añade una +regla propia para el **label**: -- **Input/control → 1:1**: `font-size-{size}` = `var(--font-size-{size})`. La fuente - sigue la escala tipográfica decidida tal cual: **12 · 14 · 16 · 18→20 · 24→28** - (xs/sm/md/lg/xl). El `md`-campo es **16px** (NO 14 — el "control compacto md=14" - vale para button/toggle, no para campos). +- **Input/control → 1:1** (como todo control): **12 · 14 · 16 · 18→20 · 24→28**. - **Label → un paso tipográfico por debajo del input**: **10 · 12 · 14 · 16 · 18**. + El label es el **único** elemento del sistema que baja un escalón a propósito — + jerarquía label↔input, no la deriva colapsada que se rechazó. | Recipe | input | label | |---|---|---| @@ -648,8 +656,21 @@ Por eso un componente **nunca inventa tamaños de icono en px**: declara empareja ambos ejes (`--size-md-font-size` + `--size-md-icon-size`). Matices por arquetipo: -- **Control compacto** (button): el icono va **un escalón por debajo** de su size - nominal — un button `md` (texto `sm` = 14px) lleva icono `sm` (16px). +- **Iconos de control siguen el font 1:1** (override 2026-06-17): el `icon-size-{size}` + de cada recipe de control referencia `var(--icon-size-{size})`, paralelo al font 1:1. + La escala de iconos preserva `icon ≈ font+2` en cuerpo (md: font 16 → icono 18). + Aplicado a **button** + **search-field**. **Excepciones**: `password-field` — + su `icon-size-*` NO es un glifo sino la **caja del botón visibility-trigger** (el glifo + es el 65%), control-coupled a propósito; `radio-cards` — el icono sigue el font del + **título** de la card. +- **Densidad ⊥ tipografía** (compact/comfortable/spacious): la densidad escala SOLO + layout — `space` (`× --density-space-scale`) + `control-height` (`× --density-control-scale`). + **`--font-size-*` y `--icon-size-*` NO llevan densidad** — solo el zoom global + `--scaling` (Radix-parity) los toca. Consecuencia: a 1:1 el icono queda **acoplado al + texto en toda densidad** (font 16 / icono 18 constantes; solo la caja del control se + aprieta: 32.4 / 36 / 40.3). Por eso un icono dimensionado desde `--control-height-*` + (density-coupled) se desacopla del texto — es un anti-patrón salvo que el elemento sea + un control (p. ej. el trigger del password-field). - **Cards / títulos** (radio-cards, empty-states): el icono **acompaña el font del título**, nunca un tamaño inflado a mano — p. ej. radio-cards = `16/16/18/18/20` (sigue su título). Para destacar más se sube el **font del título** (el icono lo diff --git a/src/uix/eidos/components/button/button.css b/src/uix/eidos/components/button/button.css index a48a97b5d..3c17529a7 100644 --- a/src/uix/eidos/components/button/button.css +++ b/src/uix/eidos/components/button/button.css @@ -313,6 +313,12 @@ /* ── Inline SVG icons inherit color + sizing ──────────────────────────── */ [data-button] :is(.eidos-button-icon, [data-button-icon]) { + /* Center the glyph in the slot. As a flex item the slot box is centered, + * but a bare inline inside aligns to the text baseline and sits ~1px + * below the button's vertical center. Flex-center it like .eidos-button-body. */ + display: inline-flex; + align-items: center; + justify-content: center; pointer-events: none; } diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 82554bd16..87f437051 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -734,9 +734,9 @@ --badge-gap-lg: var(--space-2); --badge-font-family: var(--font-ui); --badge-font-size-xs: var(--font-size-xs); - --badge-font-size-sm: var(--font-size-xs); - --badge-font-size-md: var(--font-size-sm); - --badge-font-size-lg: var(--font-size-md); + --badge-font-size-sm: var(--font-size-sm); + --badge-font-size-md: var(--font-size-md); + --badge-font-size-lg: var(--font-size-lg); --badge-font-weight: var(--font-weight-medium); --badge-line-height: var(--leading-ui); --badge-letter-spacing: 0.01em; @@ -795,9 +795,9 @@ --breadcrumb-gap-lg: var(--space-2); --breadcrumb-font-family: var(--font-ui); --breadcrumb-font-size-xs: var(--font-size-xs); - --breadcrumb-font-size-sm: var(--font-size-xs); - --breadcrumb-font-size-md: var(--font-size-sm); - --breadcrumb-font-size-lg: var(--font-size-md); + --breadcrumb-font-size-sm: var(--font-size-sm); + --breadcrumb-font-size-md: var(--font-size-md); + --breadcrumb-font-size-lg: var(--font-size-lg); --breadcrumb-line-height: var(--leading-ui); --breadcrumb-color: var(--color-content-secondary); --breadcrumb-link-radius: var(--radius-sm); @@ -1040,12 +1040,7 @@ --field-gap-xl: var(--space-3); --field-font-family: var(--font-ui); --field-label-gap: var(--space-1); - --field-label-font-size-xs: var(--font-size-xxs); - --field-label-font-size-sm: var(--font-size-xs); - --field-label-font-size-md: var(--font-size-sm); - --field-label-font-size-lg: var(--font-size-md); - --field-label-font-size-xl: var(--font-size-lg); - --field-label-font-weight: var(--font-weight-medium); + --field-label-font-weight: var(--font-weight-regular); --field-label-line-height: var(--leading-ui); --field-label-color: var(--color-content-primary); --field-label-color-invalid: var(--color-risk-text); @@ -1088,7 +1083,6 @@ --field-control-placeholder-color: var(--color-content-muted); --field-control-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color); --field-affix-color: var(--color-content-secondary); - --field-message-font-size: var(--font-size-xs); --field-message-line-height: var(--leading-ui); --field-helper-color: var(--color-content-secondary); --field-error-color: var(--color-risk-text); @@ -1419,7 +1413,7 @@ --date-field-bg: var(--color-surface-default); --date-field-color: var(--color-content-primary); --date-field-label-color: var(--color-content-secondary); - --date-field-label-font-weight: var(--font-weight-medium); + --date-field-label-font-weight: var(--font-weight-regular); --date-field-placeholder-color: var(--color-content-muted); --date-field-literal-color: var(--color-content-muted); --date-field-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color); @@ -1470,7 +1464,7 @@ --time-field-bg: var(--color-surface-default); --time-field-color: var(--color-content-primary); --time-field-label-color: var(--color-content-secondary); - --time-field-label-font-weight: var(--font-weight-medium); + --time-field-label-font-weight: var(--font-weight-regular); --time-field-placeholder-color: var(--color-content-muted); --time-field-literal-color: var(--color-content-muted); --time-field-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color); @@ -1482,7 +1476,6 @@ --time-field-segment-readonly-bg: var(--color-surface-overlay); --time-field-segment-readonly-color: var(--color-content-secondary); --time-field-segment-readonly-decoration: underline dotted var(--color-content-muted); - --time-field-segment-focus-shadow: inset 0 0 0 var(--border-width) var(--_time-field-accent-border); --time-field-transition-duration: var(--duration-fast); --time-field-transition-ease: var(--ease-default); --time-field-disabled-opacity: var(--opacity-disabled); @@ -1521,7 +1514,7 @@ --color-field-bg: var(--color-surface-default); --color-field-color: var(--color-content-primary); --color-field-label-color: var(--color-content-secondary); - --color-field-label-font-weight: var(--font-weight-medium); + --color-field-label-font-weight: var(--font-weight-regular); --color-field-placeholder-color: var(--color-content-muted); --color-field-literal-color: var(--color-content-muted); --color-field-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color); @@ -1628,9 +1621,9 @@ --button-letter-spacing: var(--tracking-normal); --button-font-size-xs: var(--font-size-xs); --button-font-size-sm: var(--font-size-sm); - --button-font-size-md: var(--font-size-sm); - --button-font-size-lg: var(--font-size-md); - --button-font-size-xl: var(--font-size-lg); + --button-font-size-md: var(--font-size-md); + --button-font-size-lg: var(--font-size-lg); + --button-font-size-xl: var(--font-size-xl); --button-font-weight-xs: var(--font-weight-medium); --button-font-weight-sm: var(--font-weight-medium); --button-font-weight-md: var(--font-weight-medium); @@ -1638,9 +1631,9 @@ --button-font-weight-xl: var(--font-weight-medium); --button-icon-size-xs: var(--icon-size-xs); --button-icon-size-sm: var(--icon-size-sm); - --button-icon-size-md: var(--icon-size-sm); - --button-icon-size-lg: var(--icon-size-md); - --button-icon-size-xl: var(--icon-size-lg); + --button-icon-size-md: var(--icon-size-md); + --button-icon-size-lg: var(--icon-size-lg); + --button-icon-size-xl: var(--icon-size-xl); --button-radius-sm: var(--radius-sm); --button-radius-md: var(--radius-md); --button-radius-lg: var(--radius-lg); @@ -1723,9 +1716,9 @@ --calendar-shadow: var(--depth-raised-shadow), var(--depth-raised-halo); --calendar-font-family: var(--font-ui); --calendar-font-size-xs: var(--font-size-xs); - --calendar-font-size-sm: var(--font-size-xs); - --calendar-font-size-md: var(--font-size-sm); - --calendar-font-size-lg: var(--font-size-sm); + --calendar-font-size-sm: var(--font-size-sm); + --calendar-font-size-md: var(--font-size-md); + --calendar-font-size-lg: var(--font-size-lg); --calendar-line-height: var(--leading-ui); --calendar-header-gap: var(--space-2); --calendar-heading-color: var(--color-content-primary); @@ -1800,9 +1793,9 @@ --pagination-control-px-lg: var(--space-3); --pagination-font-family: var(--font-ui); --pagination-font-size-xs: var(--font-size-xs); - --pagination-font-size-sm: var(--font-size-xs); - --pagination-font-size-md: var(--font-size-sm); - --pagination-font-size-lg: var(--font-size-sm); + --pagination-font-size-sm: var(--font-size-sm); + --pagination-font-size-md: var(--font-size-md); + --pagination-font-size-lg: var(--font-size-lg); --pagination-line-height: var(--leading-ui); --pagination-control-radius: var(--radius-md); --pagination-control-border-width: var(--border-width); @@ -1926,10 +1919,10 @@ --radio-group-item-gap: var(--space-3); --radio-group-font-family: var(--font-ui); --radio-group-font-size-xs: var(--font-size-xs); - --radio-group-font-size-sm: var(--font-size-xs); - --radio-group-font-size-md: var(--font-size-sm); - --radio-group-font-size-lg: var(--font-size-sm); - --radio-group-font-size-xl: var(--font-size-md); + --radio-group-font-size-sm: var(--font-size-sm); + --radio-group-font-size-md: var(--font-size-md); + --radio-group-font-size-lg: var(--font-size-lg); + --radio-group-font-size-xl: var(--font-size-xl); --radio-group-font-weight: var(--font-weight-medium); --radio-group-line-height: var(--leading-ui); --radio-group-item-label-color: var(--color-content-primary); @@ -2077,8 +2070,8 @@ --search-field-icon-size-xs: var(--icon-size-xs); --search-field-icon-size-sm: var(--icon-size-sm); --search-field-icon-size-md: var(--icon-size-md); - --search-field-icon-size-lg: var(--icon-size-md); - --search-field-icon-size-xl: var(--icon-size-lg); + --search-field-icon-size-lg: var(--icon-size-lg); + --search-field-icon-size-xl: var(--icon-size-xl); --search-field-icon-color: var(--color-content-muted); --search-field-icon-margin-inline-end: var(--space-1-5); --search-field-loading-indicator-size-xs: var(--icon-size-xs); @@ -2293,9 +2286,9 @@ --toolbar-control-gap-lg: var(--space-2); --toolbar-font-family: var(--font-ui); --toolbar-font-size-xs: var(--font-size-xs); - --toolbar-font-size-sm: var(--font-size-xs); - --toolbar-font-size-md: var(--font-size-sm); - --toolbar-font-size-lg: var(--font-size-sm); + --toolbar-font-size-sm: var(--font-size-sm); + --toolbar-font-size-md: var(--font-size-md); + --toolbar-font-size-lg: var(--font-size-lg); --toolbar-line-height: var(--leading-ui); --toolbar-radius: var(--radius-lg); --toolbar-bg: var(--color-surface-raised); @@ -2332,9 +2325,9 @@ --file-upload-control-height-sm: var(--control-height-sm); --file-upload-control-height-md: var(--control-height-md); --file-upload-control-height-lg: var(--control-height-lg); - --file-upload-font-size-sm: var(--font-size-xs); - --file-upload-font-size-md: var(--font-size-sm); - --file-upload-font-size-lg: var(--font-size-md); + --file-upload-font-size-sm: var(--font-size-sm); + --file-upload-font-size-md: var(--font-size-md); + --file-upload-font-size-lg: var(--font-size-lg); --file-upload-preview-size-sm: 32px; --file-upload-preview-size-md: 40px; --file-upload-preview-size-lg: 48px; @@ -2441,9 +2434,9 @@ --tag-group-item-gap-lg: var(--space-2); --tag-group-font-family: var(--font-ui); --tag-group-font-size-xs: var(--font-size-xs); - --tag-group-font-size-sm: var(--font-size-xs); - --tag-group-font-size-md: var(--font-size-sm); - --tag-group-font-size-lg: var(--font-size-sm); + --tag-group-font-size-sm: var(--font-size-sm); + --tag-group-font-size-md: var(--font-size-md); + --tag-group-font-size-lg: var(--font-size-lg); --tag-group-line-height: var(--leading-ui); --tag-group-color: var(--color-content-primary); --tag-group-label-color: var(--color-content-secondary); @@ -2766,9 +2759,9 @@ --stepper-indicator-size-md: var(--control-height-md); --stepper-indicator-size-lg: var(--control-height-lg); --stepper-font-family: var(--font-ui); - --stepper-font-size-sm: var(--font-size-xs); - --stepper-font-size-md: var(--font-size-sm); - --stepper-font-size-lg: var(--font-size-md); + --stepper-font-size-sm: var(--font-size-sm); + --stepper-font-size-md: var(--font-size-md); + --stepper-font-size-lg: var(--font-size-lg); --stepper-line-height: var(--leading-ui); --stepper-color: var(--color-content-primary); --stepper-list-gap: var(--space-1); @@ -3123,11 +3116,11 @@ --toggle-font-family: var(--font-ui); --toggle-line-height: var(--leading-ui); --toggle-letter-spacing: var(--tracking-normal); - --toggle-font-size-xs: var(--font-size-xxs); - --toggle-font-size-sm: var(--font-size-xs); - --toggle-font-size-md: var(--font-size-sm); - --toggle-font-size-lg: var(--font-size-md); - --toggle-font-size-xl: var(--font-size-lg); + --toggle-font-size-xs: var(--font-size-xs); + --toggle-font-size-sm: var(--font-size-sm); + --toggle-font-size-md: var(--font-size-md); + --toggle-font-size-lg: var(--font-size-lg); + --toggle-font-size-xl: var(--font-size-xl); --toggle-font-weight-xs: var(--font-weight-medium); --toggle-font-weight-sm: var(--font-weight-medium); --toggle-font-weight-md: var(--font-weight-medium); @@ -3206,9 +3199,9 @@ --tooltip-padding-x-md: var(--space-2-5); --tooltip-padding-y-lg: var(--space-2); --tooltip-padding-x-lg: var(--space-3); - --tooltip-font-size-sm: var(--font-size-xs); - --tooltip-font-size-md: var(--font-size-sm); - --tooltip-font-size-lg: var(--font-size-md); + --tooltip-font-size-sm: var(--font-size-sm); + --tooltip-font-size-md: var(--font-size-md); + --tooltip-font-size-lg: var(--font-size-lg); --tooltip-font-family: var(--font-ui); --tooltip-font-weight: var(--font-weight-regular); --tooltip-line-height: var(--leading-text); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 6761f9206..14ad2b301 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -308,9 +308,9 @@ export const THEME_BASE_RECIPE_TOKENS = { // ── Typography ──────────────────────────────────────────────────── 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'font-weight': 'var(--font-weight-medium)', 'line-height': 'var(--leading-ui)', 'letter-spacing': '0.01em', @@ -437,9 +437,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'gap-lg': 'var(--space-2)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', color: 'var(--color-content-secondary)', 'link-radius': 'var(--radius-sm)', @@ -794,12 +794,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'gap-xl': 'var(--space-3)', 'font-family': 'var(--font-ui)', 'label-gap': 'var(--space-1)', - 'label-font-size-xs': 'var(--font-size-xxs)', - 'label-font-size-sm': 'var(--font-size-xs)', - 'label-font-size-md': 'var(--font-size-sm)', - 'label-font-size-lg': 'var(--font-size-md)', - 'label-font-size-xl': 'var(--font-size-lg)', - 'label-font-weight': 'var(--font-weight-medium)', + // Label size is derived in field.css (control font − one step), not a + // per-size token — keeps it coherent with the segmented fields' calc. + 'label-font-weight': 'var(--font-weight-regular)', 'label-line-height': 'var(--leading-ui)', 'label-color': 'var(--color-content-primary)', 'label-color-invalid': 'var(--color-risk-text)', @@ -843,7 +840,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'control-focus-shadow': 'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)', 'affix-color': 'var(--color-content-secondary)', - 'message-font-size': 'var(--font-size-xs)', 'message-line-height': 'var(--leading-ui)', 'helper-color': 'var(--color-content-secondary)', 'error-color': 'var(--color-risk-text)', @@ -1240,7 +1236,7 @@ export const THEME_BASE_RECIPE_TOKENS = { bg: 'var(--color-surface-default)', color: 'var(--color-content-primary)', 'label-color': 'var(--color-content-secondary)', - 'label-font-weight': 'var(--font-weight-medium)', + 'label-font-weight': 'var(--font-weight-regular)', 'placeholder-color': 'var(--color-content-muted)', 'literal-color': 'var(--color-content-muted)', 'focus-shadow': @@ -1294,7 +1290,7 @@ export const THEME_BASE_RECIPE_TOKENS = { bg: 'var(--color-surface-default)', color: 'var(--color-content-primary)', 'label-color': 'var(--color-content-secondary)', - 'label-font-weight': 'var(--font-weight-medium)', + 'label-font-weight': 'var(--font-weight-regular)', 'placeholder-color': 'var(--color-content-muted)', 'literal-color': 'var(--color-content-muted)', 'focus-shadow': @@ -1307,7 +1303,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'segment-readonly-bg': 'var(--color-surface-overlay)', 'segment-readonly-color': 'var(--color-content-secondary)', 'segment-readonly-decoration': 'underline dotted var(--color-content-muted)', - 'segment-focus-shadow': 'inset 0 0 0 var(--border-width) var(--_time-field-accent-border)', 'transition-duration': 'var(--duration-fast)', 'transition-ease': 'var(--ease-default)', 'disabled-opacity': 'var(--opacity-disabled)' @@ -1348,7 +1343,7 @@ export const THEME_BASE_RECIPE_TOKENS = { bg: 'var(--color-surface-default)', color: 'var(--color-content-primary)', 'label-color': 'var(--color-content-secondary)', - 'label-font-weight': 'var(--font-weight-medium)', + 'label-font-weight': 'var(--font-weight-regular)', 'placeholder-color': 'var(--color-content-muted)', 'literal-color': 'var(--color-content-muted)', 'focus-shadow': @@ -1487,23 +1482,23 @@ export const THEME_BASE_RECIPE_TOKENS = { 'letter-spacing': 'var(--tracking-normal)', 'font-size-xs': 'var(--font-size-xs)', 'font-size-sm': 'var(--font-size-sm)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', - 'font-size-xl': 'var(--font-size-lg)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', + 'font-size-xl': 'var(--font-size-xl)', 'font-weight-xs': 'var(--font-weight-medium)', 'font-weight-sm': 'var(--font-weight-medium)', 'font-weight-md': 'var(--font-weight-medium)', 'font-weight-lg': 'var(--font-weight-medium)', 'font-weight-xl': 'var(--font-weight-medium)', - // Icon follows the button's font-size (canonical icon↔type correlation): - // each size's icon is the one paired with that size's text. The button - // shows compact text (font dropped a step), so the icon stays compact too. - // Density-scaled via the icon scale; never hardcoded px. + // Icon follows the button's font-size 1:1 (canonical icon↔type correlation): + // the font now scales 1:1 with the size (no longer compact), so the icon + // matches at the same step. The parallel icon scale preserves icon ≈ font+2 + // in the body sizes (md: font 16 → icon 18). Density-scaled; never hardcoded px. 'icon-size-xs': 'var(--icon-size-xs)', 'icon-size-sm': 'var(--icon-size-sm)', - 'icon-size-md': 'var(--icon-size-sm)', - 'icon-size-lg': 'var(--icon-size-md)', - 'icon-size-xl': 'var(--icon-size-lg)', + 'icon-size-md': 'var(--icon-size-md)', + 'icon-size-lg': 'var(--icon-size-lg)', + 'icon-size-xl': 'var(--icon-size-xl)', 'radius-sm': 'var(--radius-sm)', 'radius-md': 'var(--radius-md)', 'radius-lg': 'var(--radius-lg)', @@ -1681,9 +1676,9 @@ export const THEME_BASE_RECIPE_TOKENS = { shadow: 'var(--depth-raised-shadow), var(--depth-raised-halo)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-sm)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', 'header-gap': 'var(--space-2)', 'heading-color': 'var(--color-content-primary)', @@ -1768,9 +1763,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'control-px-lg': 'var(--space-3)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-sm)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', 'control-radius': 'var(--radius-md)', 'control-border-width': 'var(--border-width)', @@ -1903,10 +1898,10 @@ export const THEME_BASE_RECIPE_TOKENS = { 'item-gap': 'var(--space-3)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-sm)', - 'font-size-xl': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', + 'font-size-xl': 'var(--font-size-xl)', 'font-weight': 'var(--font-weight-medium)', 'line-height': 'var(--leading-ui)', 'item-label-color': 'var(--color-content-primary)', @@ -2111,8 +2106,8 @@ export const THEME_BASE_RECIPE_TOKENS = { 'icon-size-xs': 'var(--icon-size-xs)', 'icon-size-sm': 'var(--icon-size-sm)', 'icon-size-md': 'var(--icon-size-md)', - 'icon-size-lg': 'var(--icon-size-md)', - 'icon-size-xl': 'var(--icon-size-lg)', + 'icon-size-lg': 'var(--icon-size-lg)', + 'icon-size-xl': 'var(--icon-size-xl)', 'icon-color': 'var(--color-content-muted)', 'icon-margin-inline-end': 'var(--space-1-5)', 'loading-indicator-size-xs': 'var(--icon-size-xs)', @@ -2393,9 +2388,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'control-gap-lg': 'var(--space-2)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-sm)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', radius: 'var(--radius-lg)', bg: 'var(--color-surface-raised)', @@ -2435,9 +2430,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'control-height-sm': 'var(--control-height-sm)', 'control-height-md': 'var(--control-height-md)', 'control-height-lg': 'var(--control-height-lg)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'preview-size-sm': '32px', 'preview-size-md': '40px', 'preview-size-lg': '48px', @@ -2609,9 +2604,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'item-gap-lg': 'var(--space-2)', 'font-family': 'var(--font-ui)', 'font-size-xs': 'var(--font-size-xs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-sm)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', color: 'var(--color-content-primary)', 'label-color': 'var(--color-content-secondary)', @@ -3133,9 +3128,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'indicator-size-md': 'var(--control-height-md)', 'indicator-size-lg': 'var(--control-height-lg)', 'font-family': 'var(--font-ui)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'line-height': 'var(--leading-ui)', color: 'var(--color-content-primary)', 'list-gap': 'var(--space-1)', @@ -3596,11 +3591,11 @@ export const THEME_BASE_RECIPE_TOKENS = { 'font-family': 'var(--font-ui)', 'line-height': 'var(--leading-ui)', 'letter-spacing': 'var(--tracking-normal)', - 'font-size-xs': 'var(--font-size-xxs)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', - 'font-size-xl': 'var(--font-size-lg)', + 'font-size-xs': 'var(--font-size-xs)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', + 'font-size-xl': 'var(--font-size-xl)', 'font-weight-xs': 'var(--font-weight-medium)', 'font-weight-sm': 'var(--font-weight-medium)', 'font-weight-md': 'var(--font-weight-medium)', @@ -3832,9 +3827,9 @@ export const THEME_BASE_RECIPE_TOKENS = { 'padding-x-md': 'var(--space-2-5)', 'padding-y-lg': 'var(--space-2)', 'padding-x-lg': 'var(--space-3)', - 'font-size-sm': 'var(--font-size-xs)', - 'font-size-md': 'var(--font-size-sm)', - 'font-size-lg': 'var(--font-size-md)', + 'font-size-sm': 'var(--font-size-sm)', + 'font-size-md': 'var(--font-size-md)', + 'font-size-lg': 'var(--font-size-lg)', 'font-family': 'var(--font-ui)', 'font-weight': 'var(--font-weight-regular)', 'line-height': 'var(--leading-text)',