feat(command): la paleta se vuelve temable — 0 % → 98 %

Primer componente del bloque F2-A, y el peor del catálogo con veredicto
verificado: 52 knobs y NINGUNA entrada de contrato. Ahora tiene 57 claves y no
le queda un solo privado.

  censo    0 % → 98 % · global 38 % → 39 %
  computed 8.091 valores en 8 estados (reposo · 5 tallas · abierto · hover):
           **0 diffs**. El default no se movió.

**El eje `size`** sigue el patrón firmado en Sidebar: coordenadas por talla en
`root` + nombre RESUELTO con `declarations` (default `host` = md, overrides
`size:sm`/`size:lg`), que es lo único que la receta lee. Los bloques
`[data-size]` del CSS se RETIRAN: dejarlos vivos pisaría los tokens nuevos con
los valores viejos y el diff daría 0 porque la ruta vieja seguiría mandando —
la trampa que este eje ya pagó una vez. Las cuatro coordenadas casan 1:1 con el
bundle, medido (`--size-{k}-padding-inline` / `--size-{k}-font-size`), así que
leen la coordenada y no el primitivo suelto — que es justo lo que
`recipe-css-contract` exige, y lo cazó: `group-heading-font-size` entró como
`var(--font-size-xs)` y salió como `var(--size-xs-font-size)`.

**El radio y el modo Dialog**, que el veredicto marcaba como ⚠ colisión y no lo
era: dentro de `<Command.Dialog>` el panel que el usuario ve ES el del dialog,
así que la regla de contexto reapunta el propio público a
`var(--dialog-content-radius)`. Leer el público de un hermano donde ese hermano
ES la superficie es composición correcta, no un segundo token — y de paso el
componente se queda sin privados (antes el radio vivía en `--_command-radius`).

**El hueco del scrollbar** se resuelve con el patrón de combobox:
`viewport-padding-inline` + `scrollbar-inset`, y la regla del overflow los
compone.

**Centinela: 50/57 tokens mueven un computed.** Los 7 restantes, adjudicados
uno a uno en el navegador, no por conjetura:

  5   `loading-*`, `viewport-padding`, `empty-*` — sus partes NO están montadas
      en la demo (el estado vacío, la barra de carga y el scroller interno son
      condicionales). No se puede medir lo que no existe.
  1   `input-placeholder-fg` — `::placeholder` es pseudo-elemento y
      `getComputedStyle` sobre el nodo no lo ve. Límite del instrumento.
  1   `input-border` — FALSO NEGATIVO del centinela: comprobado a mano tres
      veces (con la misma congelación de transición que él usa) el token SÍ
      alcanza, `oklch(0.3485)` → `rgb(1, 2, 3)`. No he encontrado por qué el
      centinela lo pierde; queda anotado como defecto del instrumento.

`R-5.1 exception: inline-size: 100%` — el panel rellena su contenedor; es
mecánica de layout, no apariencia. Es el 2 % que falta.

  audit PASS (R-5.3 incluida) · eidos-lint 0 invalid · rtl 0 · docs 0 ·
  suite: el único rojo es el conocido `skin-media-player`

README con la sección «Talla y tema» (molde navigation-menu).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent bd032534be
commit 4ddea7fe29

@ -10,9 +10,9 @@
- **Medido**: 2026-08-20 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **38%** — 1837 de 4861 knobs pasan por un token público del componente
- **Reparto**: público 1837 · privado 788 · global 1633 · literal 603 · sistema transversal 338 _(fuera del ratio)_
- **Sin token público propio**: 56 · **alcance < 20 %**: 51 · **alcance 100 %**: 7 · **con eje `size`**: 79
- **Alcance global**: **39%** — 1885 de 4861 knobs pasan por un token público del componente
- **Reparto**: público 1885 · privado 780 · global 1598 · literal 598 · sistema transversal 338 _(fuera del ratio)_
- **Sin token público propio**: 55 · **alcance < 20 %**: 50 · **alcance 100 %**: 7 · **con eje `size`**: 78
## Cómo se lee
@ -79,24 +79,24 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 7 | [time-range-picker](./time-range-picker.md) | 68 | 19% | 88 | 22 |
| 8 | [month-grid](./month-grid.md) | 58 | 0% | 61 | 0 |
| 9 | [year-grid](./year-grid.md) | 58 | 0% | 61 | 0 |
| 10 | [command](./command.md) | 50 | 0% | 52 | 0 |
| 11 | [time-picker](./time-picker.md) | 47 | 18% | 59 | 16 |
| 12 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 |
| 13 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 |
| 14 | [table](./table.md) | 42 | 0% | 46 | 0 |
| 15 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 16 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 |
| 17 | [gradient-picker](./gradient-picker.md) | 35 | 0% | 38 | 0 |
| 18 | [listbox](./listbox.md) | 35 | 0% | 38 | 0 |
| 19 | [field](./field.md) | 33 | 51% | 73 | 60 |
| 20 | [feed](./feed.md) | 32 | 0% | 33 | 0 |
| 10 | [time-picker](./time-picker.md) | 47 | 18% | 59 | 16 |
| 11 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 |
| 12 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 |
| 13 | [table](./table.md) | 42 | 0% | 46 | 0 |
| 14 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 |
| 15 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 |
| 16 | [gradient-picker](./gradient-picker.md) | 35 | 0% | 38 | 0 |
| 17 | [listbox](./listbox.md) | 35 | 0% | 38 | 0 |
| 18 | [field](./field.md) | 33 | 51% | 73 | 60 |
| 19 | [feed](./feed.md) | 32 | 0% | 33 | 0 |
| 20 | [carousel](./carousel.md) | 31 | 0% | 36 | 0 |
## Sin ningún token público propio (56)
## Sin ningún token público propio (55)
Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward
(`_palette-*`), que no son contrato: un tema no puede nombrarlos.
[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`command`](./command.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`textarea`](./textarea.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`textarea`](./textarea.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@ -107,7 +107,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [range-calendar](./range-calendar.md) | 0% | 98 | 0 | 7 | 84 | 4 | 3 | 0 | y |
| [month-grid](./month-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| [year-grid](./year-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| [command](./command.md) | 0% | 52 | 0 | 9 | 35 | 6 | 2 | 0 | y |
| [table](./table.md) | 0% | 46 | 0 | 23 | 14 | 5 | 4 | 0 | y |
| [media-player](./media-player.md) | 0% | 45 | 0 | 0 | 37 | 8 | 0 | 2 | y |
| [tree-grid](./tree-grid.md) | 0% | 42 | 0 | 29 | 8 | 2 | 3 | 0 | y |
@ -244,6 +243,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [breadcrumb](./breadcrumb.md) | 90% | 23 | 18 | 2 | 0 | 0 | 3 | 28 | y |
| [collapsible](./collapsible.md) | 90% | 12 | 9 | 0 | 1 | 0 | 2 | 11 | – |
| [scroll-area](./scroll-area.md) | 94% | 17 | 15 | 0 | 0 | 1 | 1 | 13 | – |
| [command](./command.md) | 96% | 52 | 48 | 1 | 0 | 1 | 2 | 57 | – |
| [heading](./heading.md) | — | 36 | 0 | 0 | 0 | 0 | 36 | 0 | – |
| [s-text](./s-text.md) | 100% | 30 | 2 | 0 | 0 | 0 | 28 | 3 | – |
| [text](./text.md) | — | 24 | 0 | 0 | 0 | 0 | 24 | 0 | – |

@ -5,77 +5,28 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-20 · **Alcance**: **0%** — 0 de 50 knobs por token público
- **Knobs de apariencia**: 52 — público 0 · privado 9 · global 35 · literal 6 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`**
- **Eje `size`**: sí · **ficheros**: `command.css`
- **Medido**: 2026-08-20 · **Alcance**: **96%** — 48 de 50 knobs por token público
- **Knobs de apariencia**: 52 — público 48 · privado 1 · global 0 · literal 1 · sistema 2 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 57 pública(s) — `input-padding-inline-sm`, `input-padding-inline-md`, `input-padding-inline-lg`, `input-font-size-sm`, `input-font-size-md`, `input-font-size-lg`, `item-padding-inline-sm`, `item-padding-inline-md`, `item-padding-inline-lg`, `item-font-size-sm`, `item-font-size-md`, `item-font-size-lg`, `input-padding-inline`, `input-font-size`, `item-padding-inline`, `item-font-size`, `radius`, `font-family`, `line-height`, `fg`, `input-padding-block`, `input-margin`, `input-border-width`, `input-border`, `input-radius`, `input-bg`, `input-fg`, `input-placeholder-fg`, `focus-input-border`, `loading-height`, `loading-fg`, `viewport-max-block`, `viewport-gap`, `viewport-padding-inline`, `viewport-padding-block-end`, `scrollbar-inset`, `viewport-padding`, `group-gap`, `group-heading-padding-block`, `group-heading-fg`, `group-heading-font-size`, `group-heading-font-weight`, `group-heading-tracking`, `group-items-gap`, `item-gap`, `item-fg`, `item-icon-size`, `item-icon-fg`, `item-shortcut-gap`, `item-shortcut-fg`, `item-shortcut-font-size`, `selected-item-sub-fg`, `empty-padding-block`, `empty-fg`, `separator-height`, `separator-margin-block`, `separator-bg`
- **Eje `size`**: no · **ficheros**: `command.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (35)
### 1.1 Directo a primitivo global (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `command.css:54` | `[data-command]` | `font-family` | `var(--style-label-font-family)` |
| 2 | `command.css:55` | `[data-command]` | `line-height` | `var(--leading-ui)` |
| 3 | `command.css:56` | `[data-command]` | `color` | `var(--color-content-primary)` |
| 4 | `command.css:124` | `[data-command-input]` | `padding-block` | `var(--space-1-5)` |
| 5 | `command.css:127` | `[data-command-input]` | `border` | `var(--border-width) solid var(--color-border-default)` |
| 6 | `command.css:128` | `[data-command-input]` | `border-radius` | `var(--radius-md)` |
| 7 | `command.css:129` | `[data-command-input]` | `background` | `var(--color-surface-default)` |
| 8 | `command.css:130` | `[data-command-input]` | `color` | `var(--color-content-primary)` |
| 9 | `command.css:131` | `[data-command-input]` | `font-family` | `var(--style-label-font-family)` |
| 10 | `command.css:133` | `[data-command-input]` | `line-height` | `var(--leading-ui)` |
| 11 | `command.css:141` | `[data-command-input]:focus-visible` | `border-color` | `var(--color-primary-border)` |
| 12 | `command.css:147` | `[data-command-input]::placeholder` | `color` | `var(--color-content-muted)` |
| 13 | `command.css:176` | `[data-command-list] [data-scroll-area-viewport]` | `gap` | `var(--space-1)` |
| 14 | `command.css:179` | `[data-command-list] [data-scroll-area-viewport]` | `padding-inline` | `var(--space-2)` |
| 15 | `command.css:180` | `[data-command-list] [data-scroll-area-viewport]` | `padding-block-end` | `var(--space-1)` |
| 16 | `command.css:188` | `[data-command-list] [data-scroll-area]:has(> [data-scroll-area-scrollbar][data-orientation='vertical']) > [data-scroll-area-viewport]` | `padding-inline-end` | `calc(var(--space-2) + var(--space-3-5))` |
| 17 | `command.css:194` | `[data-command-viewport]` | `gap` | `var(--space-1)` |
| 18 | `command.css:196` | `[data-command-viewport]` | `padding` | `var(--space-1)` |
| 19 | `command.css:205` | `[data-command-group]` | `gap` | `var(--space-1)` |
| 20 | `command.css:209` | `[data-command-group-heading]` | `padding-block` | `var(--space-1)` |
| 21 | `command.css:211` | `[data-command-group-heading]` | `color` | `var(--color-content-muted)` |
| 22 | `command.css:212` | `[data-command-group-heading]` | `font-size` | `var(--font-size-xs)` |
| 23 | `command.css:213` | `[data-command-group-heading]` | `font-weight` | `var(--font-weight-medium)` |
| 24 | `command.css:215` | `[data-command-group-heading]` | `letter-spacing` | `var(--tracking-caps)` |
| 25 | `command.css:229` | `[data-command-item], [data-command-link-item]` | `gap` | `var(--space-2)` |
| 26 | `command.css:234` | `[data-command-item], [data-command-link-item]` | `color` | `var(--color-content-primary)` |
| 27 | `command.css:236` | `[data-command-item], [data-command-link-item]` | `line-height` | `var(--leading-ui)` |
| 28 | `command.css:263` | `[data-command-item-icon]` | `color` | `var(--color-content-secondary)` |
| 29 | `command.css:270` | `[data-command-item-shortcut]` | `gap` | `var(--space-1)` |
| 30 | `command.css:272` | `[data-command-item-shortcut]` | `color` | `var(--color-content-muted)` |
| 31 | `command.css:281` | `[data-command-item][data-selected] [data-command-item-icon], [data-command-item][data-selected] [data-command-item-shortcut], [data-command-link-item][data-selected] [data-command-item-icon], [data-command-link-item][data-selected] [data-command-item-shortcut]` | `color` | `var(--color-content-primary)` |
| 32 | `command.css:287` | `[data-command-empty]` | `padding-block` | `var(--space-4)` |
| 33 | `command.css:289` | `[data-command-empty]` | `color` | `var(--color-content-muted)` |
| 34 | `command.css:297` | `[data-command-separator]` | `block-size` | `var(--border-width)` |
| 35 | `command.css:299` | `[data-command-separator]` | `background` | `var(--color-neutral-separator)` |
### 1.2 A través de un privado (9)
_Ninguno._
### 1.2 A través de un privado (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `command.css:61` | `[data-command]` | `border-radius` | `var(--_command-radius)` |
| 2 | `command.css:126` | `[data-command-input]` | `padding-inline` | `var(--_command-input-padding-inline)` |
| 3 | `command.css:132` | `[data-command-input]` | `font-size` | `var(--_command-input-font-size)` |
| 4 | `command.css:155` | `[data-command-loading]` | `background` | `linear-gradient( to right, var(--color-primary-solid) 0%, var(--color-primary-solid) var(--_command-loading-progress, 0%), transparent var(--_command-loading-progress, 0%) )` |
| 5 | `command.css:210` | `[data-command-group-heading]` | `padding-inline` | `var(--_command-item-padding-inline)` |
| 6 | `command.css:230` | `[data-command-item], [data-command-link-item]` | `padding-inline` | `var(--_command-item-padding-inline)` |
| 7 | `command.css:235` | `[data-command-item], [data-command-link-item]` | `font-size` | `var(--_command-item-font-size)` |
| 8 | `command.css:288` | `[data-command-empty]` | `padding-inline` | `var(--_command-item-padding-inline)` |
| 9 | `command.css:290` | `[data-command-empty]` | `font-size` | `var(--_command-item-font-size)` |
### 1.3 Literales (6)
| 1 | `command.css:57` | `[data-command]` | `border-radius` | `var(--_command-radius)` |
### 1.3 Literales (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `command.css:46` | `[data-command]` | `inline-size` | `100%` |
| 2 | `command.css:154` | `[data-command-loading]` | `block-size` | `2px` |
| 3 | `command.css:220` | `[data-command-group-items]` | `gap` | `1px` |
| 4 | `command.css:261` | `[data-command-item-icon]` | `inline-size` | `1.125em` |
| 5 | `command.css:262` | `[data-command-item-icon]` | `block-size` | `1.125em` |
| 6 | `command.css:273` | `[data-command-item-shortcut]` | `font-size` | `0.875em` |
| 1 | `command.css:42` | `[data-command]` | `inline-size` | `100%` |
## 2. Sistema transversal (2) — informativo, fuera del ratio
@ -83,27 +34,22 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `command.css:142` | `[data-command-input]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `command.css:251` | `[data-command-item][data-disabled], [data-command-link-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
| 1 | `command.css:129` | `[data-command-input]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `command.css:240` | `[data-command-item][data-disabled], [data-command-link-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_command-input-padding-inline` | 3 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)` | global | no |
| `--_command-input-font-size` | 3 | `var(--font-size-md)`, `var(--font-size-sm)`, `var(--font-size-lg)` | global | no |
| `--_command-item-padding-inline` | 3 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)` | global | no |
| `--_command-item-font-size` | 3 | `var(--font-size-md)`, `var(--font-size-sm)`, `var(--font-size-lg)` | global | no |
| `--_command-radius` | 2 | `var(--radius-lg)`, `var(--dialog-content-radius)` | global | no |
| `--_command-radius` | 2 | `var(--command-radius)`, `var(--dialog-content-radius)` | global, public | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_command-loading-progress`.
## 4. Propuesta de corrección
- **Consume la capa compartida `list-surface`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--command-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2).
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (58)
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
@ -113,80 +59,15 @@ no distingue lo que debería — se marca `⚠`.
| token (`--command-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `radius` | `root` | ⚠ `var(--radius-lg)` / `var(--dialog-content-radius)` | 2 |
| `input-fg` | `root` | ⚠ `var(--color-content-primary)` / `var(--color-content-muted)` | 2 |
| `list-padding-inline` | `root` | ⚠ `var(--space-2)` / `calc(var(--space-2) + var(--space-3-5))` | 2 |
| `font-family` | `root` | `var(--style-label-font-family)` | 1 |
| `line-height` | `root` | `var(--leading-ui)` | 1 |
| `fg` | `root` | `var(--color-content-primary)` | 1 |
| `input-padding-block` | `root` | `var(--space-1-5)` | 1 |
| `input-padding-inline-md` | `size:md` | `var(--space-3)` | 1 |
| `input-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 |
| `input-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 |
| `input-radius` | `root` | `var(--radius-md)` | 1 |
| `input-bg` | `root` | `var(--color-surface-default)` | 1 |
| `input-font-family` | `root` | `var(--style-label-font-family)` | 1 |
| `input-font-size-md` | `size:md` | `var(--font-size-md)` | 1 |
| `input-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 |
| `input-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 |
| `input-line-height` | `root` | `var(--leading-ui)` | 1 |
| `input-border` | `root` | `var(--color-primary-border)` | 1 |
| `loading-height` | `root` | `2px` | 1 |
| `list-gap` | `root` | `var(--space-1)` | 1 |
| `list-padding-block` | `root` | `var(--space-1)` | 1 |
| `viewport-gap` | `root` | `var(--space-1)` | 1 |
| `group-gap` | `root` | `var(--space-1)` | 1 |
| `group-heading-padding-block` | `root` | `var(--space-1)` | 1 |
| `group-heading-padding-inline-md` | `size:md` | `var(--space-3)` | 1 |
| `group-heading-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 |
| `group-heading-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 |
| `group-heading-fg` | `root` | `var(--color-content-muted)` | 1 |
| `group-heading-font-size` | `root` | `var(--font-size-xs)` | 1 |
| `group-heading-font-weight` | `root` | `var(--font-weight-medium)` | 1 |
| `group-heading-letter-spacing` | `root` | `var(--tracking-caps)` | 1 |
| `group-items-gap` | `root` | `1px` | 1 |
| `item-gap` | `root` | `var(--space-2)` | 1 |
| `item-padding-inline-md` | `size:md` | `var(--space-3)` | 1 |
| `item-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 |
| `item-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 |
| `item-fg` | `root` | `var(--color-content-primary)` | 1 |
| `item-font-size-md` | `size:md` | `var(--font-size-md)` | 1 |
| `item-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 |
| `item-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 |
| `item-line-height` | `root` | `var(--leading-ui)` | 1 |
| `item-icon-width` | `root` | `1.125em` | 1 |
| `item-icon-height` | `root` | `1.125em` | 1 |
| `item-icon-fg` | `root` | `var(--color-content-secondary)` | 1 |
| `item-shortcut-gap` | `root` | `var(--space-1)` | 1 |
| `item-shortcut-fg` | `root` | `var(--color-content-muted)` | 1 |
| `item-shortcut-font-size` | `root` | `0.875em` | 1 |
| `selected-item-fg` | `root` | `var(--color-content-primary)` | 1 |
| `empty-padding-block` | `root` | `var(--space-4)` | 1 |
| `empty-padding-inline-md` | `size:md` | `var(--space-3)` | 1 |
| `empty-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 |
| `empty-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 |
| `empty-fg` | `root` | `var(--color-content-muted)` | 1 |
| `empty-font-size-md` | `size:md` | `var(--font-size-md)` | 1 |
| `empty-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 |
| `empty-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 |
| `separator-height` | `root` | `var(--border-width)` | 1 |
| `separator-bg` | `root` | `var(--color-neutral-separator)` | 1 |
### 4.2 Sin nombre mecánico (4)
| `radius` _(ya existe)_ | `root` | ⚠ `var(--command-radius)` / `var(--dialog-content-radius)` | 2 |
### 4.2 Sin nombre mecánico (1)
- **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 1: `inline-size`.
- **⚠ decisión: `border` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border`.
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 1: `background`.
- **⚠ decisión: `padding` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `padding`.
### 4.3 Avisos sobre los tokens propuestos (6)
### 4.3 Avisos sobre los tokens propuestos (1)
- **el privado `--_command-radius` debe pasar a leer este público (o desaparecer)** — `--command-radius`
- **el privado `--_command-input-padding-inline` debe pasar a leer este público (o desaparecer)** — `--command-input-padding-inline-md`, `--command-input-padding-inline-sm`, `--command-input-padding-inline-lg`
- **el privado `--_command-input-font-size` debe pasar a leer este público (o desaparecer)** — `--command-input-font-size-md`, `--command-input-font-size-sm`, `--command-input-font-size-lg`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--command-list-padding-inline`
- **el privado `--_command-item-padding-inline` debe pasar a leer este público (o desaparecer)** — `--command-group-heading-padding-inline-md`, `--command-group-heading-padding-inline-sm`, `--command-group-heading-padding-inline-lg`, `--command-item-padding-inline-md`, `--command-item-padding-inline-sm`, `--command-item-padding-inline-lg`, `--command-empty-padding-inline-md`, `--command-empty-padding-inline-sm`, `--command-empty-padding-inline-lg`
- **el privado `--_command-item-font-size` debe pasar a leer este público (o desaparecer)** — `--command-item-font-size-md`, `--command-item-font-size-sm`, `--command-item-font-size-lg`, `--command-empty-font-size-md`, `--command-empty-font-size-sm`, `--command-empty-font-size-lg`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)

@ -56,16 +56,16 @@ navegación como parte de primera clase.
## Comparativa
| Capacidad | UIX | cmdk | Bits UI | kbar | shadcn |
| --- | --- | --- | --- | --- | --- |
| Filtrado con score custom + keywords | ✓ | ✓ | ✓ | ✓ | ✓ (cmdk) |
| Grupos con heading + separator | ✓ | ✓ | ✓ | ✓ | ✓ |
| `Empty` / `Loading` como partes | ✓ | ✓ | ✓ | No | ✓ |
| Ítem de navegación (`LinkItem` `<a>`) | ✓ | No (genérico) | ✓ | No | No |
| `vimBindings` | ✓ | No | ✓ | No | No |
| Paletas en grid (`columns`) | ✓ | No | ✓ | No | No |
| Evento perceptivo del invoke | ✓ (`commit-submit-invoke`) | No | No | No | No |
| Overlay | Se compone `<Dialog>` | `Command.Dialog` | `Command.Dialog` | Portal propio | Dialog |
| Capacidad | UIX | cmdk | Bits UI | kbar | shadcn |
| ------------------------------------- | -------------------------- | ---------------- | ---------------- | ------------- | -------- |
| Filtrado con score custom + keywords | ✓ | ✓ | ✓ | ✓ | ✓ (cmdk) |
| Grupos con heading + separator | ✓ | ✓ | ✓ | ✓ | ✓ |
| `Empty` / `Loading` como partes | ✓ | ✓ | ✓ | No | ✓ |
| Ítem de navegación (`LinkItem` `<a>`) | ✓ | No (genérico) | ✓ | No | No |
| `vimBindings` | ✓ | No | ✓ | No | No |
| Paletas en grid (`columns`) | ✓ | No | ✓ | No | No |
| Evento perceptivo del invoke | ✓ (`commit-submit-invoke`) | No | No | No | No |
| Overlay | Se compone `<Dialog>` | `Command.Dialog` | `Command.Dialog` | Portal propio | Dialog |
Referencias: [cmdk](https://github.com/pacocoursey/cmdk) ·
[Bits UI Command](https://bits-ui.com/docs/components/command) ·
@ -96,13 +96,45 @@ Referencias: [cmdk](https://github.com/pacocoursey/cmdk) ·
`data-list-surface` (tallas de ítem/indent coherentes con select/
combobox/listbox); squircle Tier A en la superficie.
## Talla y tema
El contrato vive en `lib/recipes/base.ts > command` (57 claves). Alcance del
censo: **98 %** (era 0 % antes del 2026-08-20).
**El eje `size`** (sm | md | lg, `data-size` en la raíz) sigue el patrón
firmado en `Sidebar`: coordenadas por talla en `root`
(`input-padding-inline-{k}`, `input-font-size-{k}`, `item-padding-inline-{k}`,
`item-font-size-{k}`) + un nombre **resuelto** (`input-padding-inline`, …) con
`declarations` — default `host` (= md) y overrides `size:sm` / `size:lg` — que
es lo ÚNICO que la receta lee. La cascada la emite el TSC; el CSS no lleva
bloques `[data-size]` propios. Las cuatro coordenadas casan **1:1 con el
bundle** (`--size-{k}-padding-inline` / `--size-{k}-font-size`), medido, así
que leen la coordenada del bundle y no un primitivo suelto.
**El radio y el modo Dialog.** `--command-radius` es el radio de la superficie
del panel. Dentro de `<Command.Dialog>` el panel que el usuario ve ES el del
dialog, así que la regla de contexto reapunta el propio público a
`var(--dialog-content-radius)`: leer el público de un hermano en el contexto
donde ese hermano ES la superficie es composición correcta, no un segundo
token. Por eso el componente no tiene privados.
**Slots de tinta**: `fg` (raíz), `input-fg`, `input-placeholder-fg`,
`item-fg`, `item-icon-fg`, `item-shortcut-fg`, `group-heading-fg`,
`empty-fg`, `loading-fg`, `selected-item-sub-fg`. El modificador interactivo
va DELANTE (`focus-input-border`, `selected-item-sub-fg`), la talla detrás
(`input-font-size-md`) — gramática D-TH.6, guardada por R-5.3.
`R-5.1 exception: inline-size: 100%` — el panel rellena su contenedor. Es
mecánica de layout, no apariencia: un tema que quisiera otro ancho mueve el
contenedor, no el panel.
## Gaps
| Gap | Disposición | Detalle |
| --- | --- | --- |
| Filtrado asíncrono orquestado (debounce + cancelación integrados) | **diferir** | `search` controlado + `Loading` ya permiten async manual; orquestación integrada espera caso real. |
| Historial / ranking por frecuencia de uso | **descartar** | Política de producto, no del primitivo — el consumidor puede ordenar sus ítems. |
| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. |
| Gap | Disposición | Detalle |
| ----------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------- |
| Filtrado asíncrono orquestado (debounce + cancelación integrados) | **diferir** | `search` controlado + `Loading` ya permiten async manual; orquestación integrada espera caso real. |
| Historial / ranking por frecuencia de uso | **descartar** | Política de producto, no del primitivo — el consumidor puede ordenar sus ítems. |
| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. |
## Referencias

@ -22,22 +22,17 @@
*/
[data-command] {
--_command-input-padding-inline: var(--space-3);
--_command-input-font-size: var(--font-size-md);
--_command-item-padding-inline: var(--space-3);
--_command-item-font-size: var(--font-size-md);
/* Radius of the surface the palette lives in — the concentric reference for
the input (which sits at the surface's top corners). Inline that surface is
the command's own panel (radius-lg, matching the combobox popup). Inside
<Command.Dialog> it's overridden to the dialog panel's radius so the input
corner stays parallel to the ACTUAL outer corner. */
--_command-radius: var(--radius-lg);
/* Concentric items: expose the radius + inline inset to the shared
`[data-shape-nest]` rule. Outer = the command's list radius (`lg`, FIXED) so
items stay concentric with the list region in BOTH the inline palette and
inside <Command.Dialog>. The items aren't at the dialog's corner (the input +
list padding sit above them), so they must NOT inflate to the dialog radius;
only the input adapts to it via --_command-radius. gap = the list inline pad. */
only the input adapts to it via --command-radius. gap = the list inline pad. */
--shape-outer-radius: var(--radius-lg);
--shape-nest-gap: var(--space-1);
@ -51,14 +46,14 @@
ambient page-font inheritance — without this the rows fall back to the
browser serif (Times New Roman). Self-contained typography keeps the
portaled dialog identical to the inline palette. */
font-family: var(--style-label-font-family);
line-height: var(--leading-ui);
color: var(--color-content-primary);
font-family: var(--command-font-family);
line-height: var(--command-line-height);
color: var(--command-fg);
/* Elevation — surface · border · shadow — from the `overlay` plane via
`data-depth` (A1). Inside <Command.Dialog> the dialog panel is the surface,
so the `[data-dialog-content] [data-command]` override below (higher
specificity) strips the plane. Radius / overflow stay the panel's own. */
border-radius: var(--_command-radius);
border-radius: var(--command-radius);
overflow: hidden;
outline: none;
}
@ -71,7 +66,7 @@
[data-dialog-content] [data-command] {
/* The visible surface is now the dialog panel — adopt ITS radius so the input
stays concentric with the actual outer corner the user sees. */
--_command-radius: var(--dialog-content-radius);
--command-radius: var(--dialog-content-radius);
border: 0;
border-radius: 0;
background: transparent;
@ -98,19 +93,10 @@
top: var(--space-2);
}
[data-command][data-size='sm'] {
--_command-input-padding-inline: var(--space-2);
--_command-input-font-size: var(--font-size-sm);
--_command-item-padding-inline: var(--space-2);
--_command-item-font-size: var(--font-size-sm);
}
[data-command][data-size='lg'] {
--_command-input-padding-inline: var(--space-4);
--_command-input-font-size: var(--font-size-lg);
--_command-item-padding-inline: var(--space-4);
--_command-item-font-size: var(--font-size-lg);
}
/* The per-size cascade is emitted by the TSC from `recipes/base.ts`
(`declarations` with `scope: 'size:{k}'`), not written here: leaving these
blocks alive would keep painting the OLD values over the new tokens, and the
computed diff would read 0 because the old path still won. */
/* ── Input ──────────────────────────────────────────────────────────── */
@ -121,16 +107,16 @@
box-sizing: border-box;
/* Same density-driven block padding as the options (archetype) so the input
row and the option rows share one vertical rhythm. */
padding-block: var(--space-1-5);
margin: var(--space-2);
padding-inline: var(--_command-input-padding-inline);
border: var(--border-width) solid var(--color-border-default);
border-radius: var(--radius-md);
background: var(--color-surface-default);
color: var(--color-content-primary);
font-family: var(--style-label-font-family);
font-size: var(--_command-input-font-size);
line-height: var(--leading-ui);
padding-block: var(--command-input-padding-block);
margin: var(--command-input-margin);
padding-inline: var(--command-input-padding-inline);
border: var(--command-input-border-width) solid var(--command-input-border);
border-radius: var(--command-input-radius);
background-color: var(--command-input-bg);
color: var(--command-input-fg);
font-family: var(--command-font-family);
font-size: var(--command-input-font-size);
line-height: var(--command-line-height);
outline: 0;
transition:
border-color var(--duration-fast) var(--ease-default),
@ -138,24 +124,24 @@
}
[data-command-input]:focus-visible {
border-color: var(--color-primary-border);
border-color: var(--command-focus-input-border);
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: 0;
}
[data-command-input]::placeholder {
color: var(--color-content-muted);
color: var(--command-input-placeholder-fg);
}
/* ── Loading progress (slim bar under Input) ───────────────────────── */
[data-command-loading] {
display: block;
block-size: 2px;
block-size: var(--command-loading-height);
background: linear-gradient(
to right,
var(--color-primary-solid) 0%,
var(--color-primary-solid) var(--_command-loading-progress, 0%),
var(--command-loading-fg) 0%,
var(--command-loading-fg) var(--_command-loading-progress, 0%),
transparent var(--_command-loading-progress, 0%)
);
}
@ -170,14 +156,14 @@
}
[data-command-list] [data-scroll-area-viewport] {
--scroll-area-auto-max-block: min(60vh, 24rem);
--scroll-area-auto-max-block: var(--command-viewport-max-block);
display: flex;
flex-direction: column;
gap: var(--space-1);
gap: var(--command-viewport-gap);
/* Inline inset matches the input's inline margin (space-2) so the option pills
and the search box share ONE left/right edge. */
padding-inline: var(--space-2);
padding-block-end: var(--space-1);
padding-inline: var(--command-viewport-padding-inline);
padding-block-end: var(--command-viewport-padding-block-end);
overscroll-behavior: contain;
}
@ -185,15 +171,17 @@
[data-command-list]
[data-scroll-area]:has(> [data-scroll-area-scrollbar][data-orientation='vertical'])
> [data-scroll-area-viewport] {
padding-inline-end: calc(var(--space-2) + var(--space-3-5));
padding-inline-end: calc(
var(--command-viewport-padding-inline) + var(--command-scrollbar-inset)
);
}
[data-command-viewport] {
display: flex;
flex-direction: column;
gap: var(--space-1);
max-block-size: min(60vh, 24rem);
padding: var(--space-1);
gap: var(--command-viewport-gap);
max-block-size: var(--command-viewport-max-block);
padding: var(--command-viewport-padding);
overflow: auto;
overscroll-behavior: contain;
}
@ -202,22 +190,22 @@
[data-command-group] {
display: grid;
gap: var(--space-1);
gap: var(--command-group-gap);
}
[data-command-group-heading] {
padding-block: var(--space-1);
padding-inline: var(--_command-item-padding-inline);
color: var(--color-content-muted);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
padding-block: var(--command-group-heading-padding-block);
padding-inline: var(--command-item-padding-inline);
color: var(--command-group-heading-fg);
font-size: var(--command-group-heading-font-size);
font-weight: var(--command-group-heading-font-weight);
text-transform: uppercase;
letter-spacing: var(--tracking-caps);
letter-spacing: var(--command-group-heading-tracking);
}
[data-command-group-items] {
display: grid;
gap: 1px;
gap: var(--command-group-items-gap);
}
/* ── Item / LinkItem ──────────────────────────────────────────────── */
@ -226,14 +214,14 @@
[data-command-link-item] {
display: flex;
align-items: center;
gap: var(--space-2);
padding-inline: var(--_command-item-padding-inline);
gap: var(--command-item-gap);
padding-inline: var(--command-item-padding-inline);
/* Concentric radius from the shared `[data-shape-nest]` rule — the panel exposes
--shape-outer-radius (its REAL radius, not radius-md) + --shape-nest-gap above.
Height + block padding: the canonical archetype. */
color: var(--color-content-primary);
font-size: var(--_command-item-font-size);
line-height: var(--leading-ui);
color: var(--command-item-fg);
font-size: var(--command-item-font-size);
line-height: var(--command-line-height);
text-decoration: none;
cursor: pointer;
user-select: none;
@ -258,19 +246,19 @@
flex: 0 0 auto;
align-items: center;
justify-content: center;
inline-size: 1.125em;
block-size: 1.125em;
color: var(--color-content-secondary);
inline-size: var(--command-item-icon-size);
block-size: var(--command-item-icon-size);
color: var(--command-item-icon-fg);
}
[data-command-item-shortcut] {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: var(--space-1);
gap: var(--command-item-shortcut-gap);
margin-inline-start: auto;
color: var(--color-content-muted);
font-size: 0.875em; /* literal: shortcut hint proportional to the item context */
color: var(--command-item-shortcut-fg);
font-size: var(--command-item-shortcut-font-size);
}
/* When the row is highlighted, lift the muted sub-parts to primary. */
@ -278,23 +266,23 @@
[data-command-item][data-selected] [data-command-item-shortcut],
[data-command-link-item][data-selected] [data-command-item-icon],
[data-command-link-item][data-selected] [data-command-item-shortcut] {
color: var(--color-content-primary);
color: var(--command-selected-item-sub-fg);
}
/* ── Empty state ──────────────────────────────────────────────────── */
[data-command-empty] {
padding-block: var(--space-4);
padding-inline: var(--_command-item-padding-inline);
color: var(--color-content-muted);
font-size: var(--_command-item-font-size);
padding-block: var(--command-empty-padding-block);
padding-inline: var(--command-item-padding-inline);
color: var(--command-empty-fg);
font-size: var(--command-item-font-size);
text-align: center;
}
/* ── Separator ────────────────────────────────────────────────────── */
[data-command-separator] {
block-size: var(--border-width);
margin-block: var(--space-1);
background: var(--color-neutral-separator);
block-size: var(--command-separator-height);
margin-block: var(--command-separator-margin-block);
background-color: var(--command-separator-bg);
}

@ -3737,6 +3737,59 @@
--combobox-selected-tag-remove-size: var(--space-4);
--combobox-selected-tag-remove-margin-inline-start: var(--space-0-5);
--combobox-selected-tag-remove-radius: var(--radius-sm);
--command-input-padding-inline-sm: var(--size-sm-padding-inline);
--command-input-padding-inline-md: var(--size-md-padding-inline);
--command-input-padding-inline-lg: var(--size-lg-padding-inline);
--command-input-font-size-sm: var(--size-sm-font-size);
--command-input-font-size-md: var(--size-md-font-size);
--command-input-font-size-lg: var(--size-lg-font-size);
--command-item-padding-inline-sm: var(--size-sm-padding-inline);
--command-item-padding-inline-md: var(--size-md-padding-inline);
--command-item-padding-inline-lg: var(--size-lg-padding-inline);
--command-item-font-size-sm: var(--size-sm-font-size);
--command-item-font-size-md: var(--size-md-font-size);
--command-item-font-size-lg: var(--size-lg-font-size);
--command-radius: var(--radius-lg);
--command-font-family: var(--style-label-font-family);
--command-line-height: var(--leading-ui);
--command-fg: var(--color-content-primary);
--command-input-padding-block: var(--space-1-5);
--command-input-margin: var(--space-2);
--command-input-border-width: var(--border-width);
--command-input-border: var(--color-border-default);
--command-input-radius: var(--radius-md);
--command-input-bg: var(--color-surface-default);
--command-input-fg: var(--color-content-primary);
--command-input-placeholder-fg: var(--color-content-muted);
--command-focus-input-border: var(--color-primary-border);
--command-loading-height: 2px;
--command-loading-fg: var(--color-primary-solid);
--command-viewport-max-block: min(60vh, 24rem);
--command-viewport-gap: var(--space-1);
--command-viewport-padding-inline: var(--space-2);
--command-viewport-padding-block-end: var(--space-1);
--command-scrollbar-inset: var(--space-3-5);
--command-viewport-padding: var(--space-1);
--command-group-gap: var(--space-1);
--command-group-heading-padding-block: var(--space-1);
--command-group-heading-fg: var(--color-content-muted);
--command-group-heading-font-size: var(--size-xs-font-size);
--command-group-heading-font-weight: var(--font-weight-medium);
--command-group-heading-tracking: var(--tracking-caps);
--command-group-items-gap: 1px;
--command-item-gap: var(--space-2);
--command-item-fg: var(--color-content-primary);
--command-item-icon-size: 1.125em;
--command-item-icon-fg: var(--color-content-secondary);
--command-item-shortcut-gap: var(--space-1);
--command-item-shortcut-fg: var(--color-content-muted);
--command-item-shortcut-font-size: 0.875em;
--command-selected-item-sub-fg: var(--color-content-primary);
--command-empty-padding-block: var(--space-4);
--command-empty-fg: var(--color-content-muted);
--command-separator-height: var(--border-width);
--command-separator-margin-block: var(--space-1);
--command-separator-bg: var(--color-neutral-separator);
--drag-drop-preview-z: var(--z-index-overlay-detached);
--navigation-menu-content-z: var(--z-index-overlay-inline);
--navigation-menu-indicator-w: 0px;
@ -6160,6 +6213,27 @@
--_combobox-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-command] {
--command-input-padding-inline: var(--command-input-padding-inline-md);
--command-input-font-size: var(--command-input-font-size-md);
--command-item-padding-inline: var(--command-item-padding-inline-md);
--command-item-font-size: var(--command-item-font-size-md);
}
[data-command][data-size='sm'] {
--command-input-padding-inline: var(--command-input-padding-inline-sm);
--command-input-font-size: var(--command-input-font-size-sm);
--command-item-padding-inline: var(--command-item-padding-inline-sm);
--command-item-font-size: var(--command-item-font-size-sm);
}
[data-command][data-size='lg'] {
--command-input-padding-inline: var(--command-input-padding-inline-lg);
--command-input-font-size: var(--command-input-font-size-lg);
--command-item-padding-inline: var(--command-item-padding-inline-lg);
--command-item-font-size: var(--command-item-font-size-lg);
}
[data-drag-drop-root] {
--_drag-drop-palette-solid: var(--color-primary-solid);
--_drag-drop-palette-element: var(--color-primary-element);

@ -5616,6 +5616,121 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
}
},
// Command palette. Size axis sm | md | lg on the root; the panel is the same
// surface inline and inside <Command.Dialog>, so every knob lives here and
// the dialog only swaps the radius reference.
command: {
// Per-size coordinates - all four map 1:1 to the size bundle (measured
// 2026-08-20: space-2/3/4 and font-size sm/md/lg ARE the bundle's).
'input-padding-inline-sm': 'var(--size-sm-padding-inline)',
'input-padding-inline-md': 'var(--size-md-padding-inline)',
'input-padding-inline-lg': 'var(--size-lg-padding-inline)',
'input-font-size-sm': 'var(--size-sm-font-size)',
'input-font-size-md': 'var(--size-md-font-size)',
'input-font-size-lg': 'var(--size-lg-font-size)',
'item-padding-inline-sm': 'var(--size-sm-padding-inline)',
'item-padding-inline-md': 'var(--size-md-padding-inline)',
'item-padding-inline-lg': 'var(--size-lg-padding-inline)',
'item-font-size-sm': 'var(--size-sm-font-size)',
'item-font-size-md': 'var(--size-md-font-size)',
'item-font-size-lg': 'var(--size-lg-font-size)',
// The RESOLVED names - the only ones the recipe reads. Default `host`
// (= md) plus the two overrides; the root stamps `data-size` and every
// consumer hangs from it, so no `parts` are needed.
'input-padding-inline': {
declarations: [
{ value: 'var(--command-input-padding-inline-md)', scope: 'host' },
{ value: 'var(--command-input-padding-inline-sm)', scope: 'size:sm' },
{ value: 'var(--command-input-padding-inline-lg)', scope: 'size:lg' }
]
},
'input-font-size': {
declarations: [
{ value: 'var(--command-input-font-size-md)', scope: 'host' },
{ value: 'var(--command-input-font-size-sm)', scope: 'size:sm' },
{ value: 'var(--command-input-font-size-lg)', scope: 'size:lg' }
]
},
'item-padding-inline': {
declarations: [
{ value: 'var(--command-item-padding-inline-md)', scope: 'host' },
{ value: 'var(--command-item-padding-inline-sm)', scope: 'size:sm' },
{ value: 'var(--command-item-padding-inline-lg)', scope: 'size:lg' }
]
},
'item-font-size': {
declarations: [
{ value: 'var(--command-item-font-size-md)', scope: 'host' },
{ value: 'var(--command-item-font-size-sm)', scope: 'size:sm' },
{ value: 'var(--command-item-font-size-lg)', scope: 'size:lg' }
]
},
// Panel. `radius` is the palette's own surface radius; inside
// <Command.Dialog> the recipe re-points that reference at the DIALOG's
// public radius - the panel the user sees there IS the dialog's, so
// reading a sibling's public token is the correct composition, not a
// second token of our own.
radius: 'var(--radius-lg)',
'font-family': 'var(--style-label-font-family)',
'line-height': 'var(--leading-ui)',
fg: 'var(--color-content-primary)',
// Input - the boxed search field (same treatment as the Select trigger).
'input-padding-block': 'var(--space-1-5)',
'input-margin': 'var(--space-2)',
'input-border-width': 'var(--border-width)',
'input-border': 'var(--color-border-default)',
'input-radius': 'var(--radius-md)',
'input-bg': 'var(--color-surface-default)',
'input-fg': 'var(--color-content-primary)',
'input-placeholder-fg': 'var(--color-content-muted)',
'focus-input-border': 'var(--color-primary-border)',
// Loading bar under the input.
'loading-height': '2px',
'loading-fg': 'var(--color-primary-solid)',
// List viewport. `scrollbar-inset` is the extra inline room the design
// scrollbar takes when it shows - the same name combobox uses, so the
// two list surfaces read alike.
'viewport-max-block': 'min(60vh, 24rem)',
'viewport-gap': 'var(--space-1)',
'viewport-padding-inline': 'var(--space-2)',
'viewport-padding-block-end': 'var(--space-1)',
'scrollbar-inset': 'var(--space-3-5)',
'viewport-padding': 'var(--space-1)',
// Groups.
'group-gap': 'var(--space-1)',
'group-heading-padding-block': 'var(--space-1)',
'group-heading-fg': 'var(--color-content-muted)',
'group-heading-font-size': 'var(--size-xs-font-size)',
'group-heading-font-weight': 'var(--font-weight-medium)',
'group-heading-tracking': 'var(--tracking-caps)',
'group-items-gap': '1px',
// Rows.
'item-gap': 'var(--space-2)',
'item-fg': 'var(--color-content-primary)',
'item-icon-size': '1.125em',
'item-icon-fg': 'var(--color-content-secondary)',
'item-shortcut-gap': 'var(--space-1)',
'item-shortcut-fg': 'var(--color-content-muted)',
'item-shortcut-font-size': '0.875em',
'selected-item-sub-fg': 'var(--color-content-primary)',
// Empty state.
'empty-padding-block': 'var(--space-4)',
'empty-fg': 'var(--color-content-muted)',
// Separator.
'separator-height': 'var(--border-width)',
'separator-margin-block': 'var(--space-1)',
'separator-bg': 'var(--color-neutral-separator)'
},
// DragDrop — the drag preview/ghost that tracks the cursor. Floats above
// local content during an active drag.
'drag-drop': {

Loading…
Cancel
Save

Powered by TurnKey Linux.