docs(eidos): THEMING §38 state-layer + §32 outline-on-surfaces + TOC

Documents this session's reference-grade coherence work in the canonical theming docs
(framework changes land in the framework's own docs, same pass):

- New §38 "Capa de estado (state-layer) --state-*": the MD3 token set
  (hover 8% / press+selected 12%, currentColor -> theme-adaptive), the neutral-vs-valenced
  doctrine, the overlay mechanism (transparent base vs filled gradient), the rollout
  (accordion pilot + 19 comps + transversal fold of the archetype trigger/item rules),
  the deferred cleanup. The ~168-hover unification had no doc until now.
- §32 (focus ring): new subsection "Outline en superficies" -- why box-shadow dies under
  forced-colors/overflow + the hardcoded surface-default gap (misaligned halo on planes),
  the 5 migrated components (ab62cca7), fields kept on box-shadow (inner-ring), the now
  orphan --focus-ring token.
- TOC completed (§36 / §37 / §38 were missing) + revision date -> 2026-06-29.
- eidos/README.md reference table: focus-ring / touch-target / state-layer rows.
active-uix
dev 3 months ago
parent 353ad59586
commit 54ecc84e2d

@ -559,6 +559,9 @@ Resumen rápido de lo que cubre, para no duplicar aquí:
| **Salida wide-gamut OKLCH default-on (hex fallback + `oklch()` sibling)** | §27 |
| **a11y forced-colors (focus outline fallback) + ramp de bordes (slot 6→7)** | §28 |
| **Canon de escalas — auditoría de theming: blur · inset-shadow/ring · gradientes · breakpoints+container · opacidad · border-width · tracking (2026-06-15)** | §35 |
| **Focus ring: dos anillos parametrizados (campos, box-shadow) + `outline` en superficies (forced-colors / overflow / gap-safe) (2026-06-29)** | §32 |
| **Touch-target — 44px en táctil, gated por `pointer: coarse` (2026-06-28)** | §37 |
| **Capa de estado (state-layer) `--state-*` — feedback neutro unificado (MD3, theme-adaptive) (2026-06-28)** | §38 |
Lo que sigue en este README son las decisiones operativas de la **capa
visual como módulo** (typography sourcing, picker patterns, API

@ -64,6 +64,9 @@
33. [Glifos de stepper themeables (`spin-field`)](#33-glifos-de-stepper-themeables-spin-field--2026-06-11)
34. [`spin-field` — visual compartido del stepper-field](#34-spin-field--visual-compartido-del-stepper-field-number-field--css-field--2026-06-11)
35. [Canon de escalas — auditoría de theming (2026-06-15)](#35-canon-de-escalas--auditoría-de-theming-2026-06-15)
36. [Gap canónico trigger→panel — offset token-driven (2026-06-22)](#36-gap-canónico-triggerpanel--offset-token-driven-2026-06-22)
37. [Touch-target — 44px en táctil, gated por puntero (2026-06-28)](#37-touch-target--44px-en-táctil-gated-por-puntero-2026-06-28)
38. [Capa de estado (state-layer) — feedback neutro unificado (2026-06-28)](#38-capa-de-estado-state-layer--feedback-neutro-unificado-2026-06-28)
---
@ -2160,6 +2163,34 @@ consumir `accent-border` en el anillo del campo. **Hoy** ambos usan el `--focus-
genérico (consistente con el resto), así que `data-color` no tiñe su foco — decisión
deliberada de la unificación.
### Outline en superficies — `box-shadow` muere en HCM/overflow (2026-06-29)
El anillo `box-shadow` tiene tres fragilidades en **controles autónomos sobre una superficie**
(no campos): (1) `box-shadow` **desaparece bajo forced-colors / HCM** (§28); (2) lo **recorta**
un ancestro `overflow: hidden`; (3) su capa de hueco hardcodea `var(--color-surface-default)`
(arriba), así que sobre un plano `raised` / `overlay` / relleno el hueco **no casa** con el
fondo → halo desalineado.
Por eso el tier de **controles de superficie** usa `outline`:
```
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset); /* o 0 flush para input / scrollbar */
```
`outline` sigue el `border-radius` en todo navegador evergreen, **no** lo recorta `overflow`,
y el fallback de forced-colors ya mapea `outline` (§28). Es el patrón de `button` / `card` y
~40 componentes. Los **últimos 5** en box-shadow se migraron en `ab62cca7`: `command-input`,
`collapsible-trigger`, `scroll-area-scrollbar`, `toggle`, `splitter`.
**Los campos (input / textarea / segmentos) SÍ siguen en box-shadow** — necesitan el anillo
INTERIOR parametrizable (`--focus-ring-inner-width`) que `outline`, al ser una sola línea, no
puede dar. El modelo de dos anillos de arriba es para ellos.
**Orphan**: tras migrar los 5, el token `--focus-ring` (box-shadow) quedó **sin consumidores
en CSS** (solo lo citan docs). Se deja como token público de foundation; podarlo es decisión
de API aparte (junto con los `--{x}-bg-hover` huérfanos, mismo blocker de `base.css`).
## 33. Glifos de stepper themeables (`spin-field`) — 2026-06-11
Los botones increment/decrement pintan su glifo desde un **token**, no desde markup
@ -2376,6 +2407,72 @@ Commits `a265d39a` (controles) · `c0904fc6` (filas) · `2750b9ce` (fila de radi
---
**Última revisión**: 2026-06-28. Si algo en este doc no coincide con
## 38. Capa de estado (state-layer) — feedback neutro unificado (2026-06-28)
El feedback neutro de interacción (hover / press / selected de un control **sin valencia**)
estaba implementado de **~5 maneras distintas** en ~60 archivos: surface-swap
(`background: var(--color-surface-raised)`), `color-mix(currentColor X%)` ad-hoc, opacity-dim,
tokens `--{x}-bg-hover` bespoke por componente… El `ARCHETYPE_COHERENCE_AUDIT` (Arq. 8) y el
plugin de diseño marcaron lo mismo 🔴: ~168 reglas `:hover` resolviendo el MISMO efecto de
cinco formas.
**Solución — un único state-layer (modelo MD3), parametrizado a nivel de tema**
(`archetypes.css > :root`):
```
--state-hover: color-mix(in srgb, currentColor 8%, transparent);
--state-press: color-mix(in srgb, currentColor 12%, transparent);
--state-selected: color-mix(in srgb, currentColor 12%, transparent);
```
Basado en `currentColor` → **theme-adaptive sin valores por-tema**: el mismo token tiñe
correcto en light y en dark (donde el surface-swap fijo perdía contraste — ganancia neta).
Es el state-layer de Material Design 3 (hover 8% · press/selected 12%).
### Doctrina — qué tier usa el state-layer
- **Tier neutro / ghost** (la mayoría: triggers, items, ghost, controles sin valencia) →
**state-layer**. Es el dueño del feedback neutro.
- **Tier valenced** (solid / soft por color) → **mantiene su hover de paleta** en el recipe
(sistema de Button: `solid→solid-hover`, `soft→element`). NO se toca — es el patrón de
referencia (Radix/Material), no bespoke.
- El shift de **color de TEXTO** (`--{x}-color-hover`) **se conserva** por componente — el
state-layer solo reemplaza el idioma de **fondo**.
### Mecanismo — overlay, no replace
- **Base transparente** (la mayoría): `background: var(--state-hover)`.
- **Base rellena** (tiene `background-color` propio): overlay en capa →
`background-image: linear-gradient(var(--state-hover), var(--state-hover))` — pinta el tinte
SOBRE el fondo base sin perderlo (filled-safe). Es la forma usada en el rollout.
### Cobertura
- **Pilot**: `accordion-trigger` (`a13ca387`).
- **Rollout**: 19 componentes neutros (`7de6c76b`) — collapsible, breadcrumb, calendar,
pagination, toolbar, file-upload, tag-group, editable, stepper, spin-field, select,
radio-cards, … (cada `--{x}-bg-hover` neutro → `--state-hover`).
- **Fold transversal** (`e7e4870d`) — las **dos reglas neutras canónicas de `archetypes.css`**
pasan al state-layer:
- `[data-archetype='trigger']:hover` — el opacity-dim (`0.85`) → tinte `--state-hover` (el
dim atenuaba también el texto; el tinte no).
- El highlight de `item` / `option` (hover/focus/highlighted/selected en dropdown · context ·
select · combobox · listbox · command) — `surface-raised` → `--state-hover` (conservando
`color: content-primary`).
### Pendiente
- Poda de los `--{x}-bg-hover` huérfanos en `lib/recipes/base.ts` (sin uso tras el rollout) +
guard test (ningún recipe neutro declara su propio `--{x}-bg-hover`). Diferido por un
entanglement de `base.css` con trabajo concurrente.
- `tabs` + `color-picker` (hover bespoke) — diferidos por el mismo motivo.
**Doctrina**: el feedback neutro es **un concepto de tema, no de componente** — paralelo
exacto al focus ring (§32). Un state-layer definido una vez y parametrizado; los componentes
no reinventan su hover.
---
**Última revisión**: 2026-06-29. Si algo en este doc no coincide con
el código, el código gana — pero abre un issue para que actualicemos
el doc.

Loading…
Cancel
Save

Powered by TurnKey Linux.