# Tokens

> Color, tipografía, radios y sombras. Los valores salen de `tokens/geist.json` y de `theme.css`.

La paleta, los radios y las sombras por defecto de Tailwind están **reseteados**: `bg-blue-50` o `shadow-md` no compilan. Solo existen los tokens del paquete.

Los valores viven en `tokens/geist.json` y se compilan a `src/styles/colors.css` con `npm run tokens`; un test falla si el CSS quedó desactualizado. Las tablas de esta página se generan de esos mismos archivos.

## Fondos: página, superficie y banda

Son tres roles distintos y cada uno tiene su token. Elegir mal se nota sobre todo en oscuro, donde la página es negro puro:

| Token | Rol | Claro | Oscuro |
|---|---|---|---|
| `bg-background` | La página. `body` (ya lo pone el paquete) y la raíz del `AppShell`. | #ffffff | #000000 |
| `bg-background-100` | La superficie: lo que flota sobre la página. Input, Select, Textarea, popup de menú, Popover, Dialog, Sheet, Card, Alert, Toast, barra mobile del shell. | #ffffff | #0a0a0a |
| `bg-background-200` | El fondo sutil / banda: Sidebar, `thead`/`tfoot` de Table, `Card variant="subtle"`, `EmptyState`. | #fafafa | #0a0a0a |

**La regla:** si el elemento **es** la página, `bg-background`; si flota **sobre** ella, `bg-background-100`.

Los valores de oscuro son los de vercel.com medidos con `getComputedStyle` (contact/sales, 2026-09-22): `--ds-background-100: hsla(0,0%,4%)` = `#0a0a0a` para las superficies y `--ds-background-200: hsla(0,0%,0%)` = `#000` para la página. `background-200` es siempre el tono que **no** es el de la página: en claro baja a `#fafafa`, en oscuro no puede bajar de `#000` y sube a `#0a0a0a`. Por eso en oscuro coincide con `background-100`: Geist tiene dos fondos por tema, no tres.

## Color

Diez pasos por familia, de `100` (el más claro en tema claro) a `1000`. La convención de Geist: **100–400 son fondos, 500–700 son bordes y elementos, 800–1000 son texto**. El contraste de `gray-900` sobre cualquier fondo de la misma familia llega a AA.

### gray

| Paso | Claro | Oscuro |
|---|---|---|
| `gray-100` | #f2f2f2 | #1a1a1a |
| `gray-200` | #ebebeb | #1f1f1f |
| `gray-300` | #e6e6e6 | #292929 |
| `gray-400` | #eaeaea | #2e2e2e |
| `gray-500` | #c9c9c9 | #454545 |
| `gray-600` | #a8a8a8 | #878787 |
| `gray-700` | #8f8f8f | #8f8f8f |
| `gray-800` | #7d7d7d | #7d7d7d |
| `gray-900` | #4d4d4d | #a0a0a0 |
| `gray-1000` | #171717 | #ededed |

### gray-alpha

| Paso | Claro | Oscuro |
|---|---|---|
| `gray-alpha-100` | #0000000d | #ffffff12 |
| `gray-alpha-200` | #00000015 | #ffffff17 |
| `gray-alpha-300` | #0000001a | #ffffff21 |
| `gray-alpha-400` | #00000014 | #ffffff24 |
| `gray-alpha-500` | #00000036 | #ffffff3d |
| `gray-alpha-600` | #0000003d | #ffffff82 |
| `gray-alpha-700` | #00000070 | #ffffff8a |
| `gray-alpha-800` | #00000082 | #ffffff78 |
| `gray-alpha-900` | #000000b3 | #ffffff9c |
| `gray-alpha-1000` | #000000e8 | #ffffffeb |

### blue

| Paso | Claro | Oscuro |
|---|---|---|
| `blue-100` | #f0f7ff | #06193a |
| `blue-200` | #eaf4ff | #022248 |
| `blue-300` | #e0efff | #002f62 |
| `blue-400` | #cce7ff | #003771 |
| `blue-500` | #97ccff | #004287 |
| `blue-600` | #51aeff | #0090ff |
| `blue-700` | #0070f7 | #0071f6 |
| `blue-800` | #005edc | #005fd8 |
| `blue-900` | #0064e2 | #50a8ff |
| `blue-1000` | #002453 | #ebf6ff |

### red

| Paso | Claro | Oscuro |
|---|---|---|
| `red-100` | #ffeef0 | #330a11 |
| `red-200` | #ffe9ea | #440d13 |
| `red-300` | #ffe4e5 | #5d0e17 |
| `red-400` | #ffd8d7 | #6f101b |
| `red-500` | #ffb5b6 | #88151f |
| `red-600` | #ff6a6e | #f32e40 |
| `red-700` | #fc0035 | #f13242 |
| `red-800` | #e70022 | #e2162a |
| `red-900` | #d60020 | #ff5e63 |
| `red-1000` | #46000c | #ffeaed |

### amber

| Paso | Claro | Oscuro |
|---|---|---|
| `amber-100` | #fff6e1 | #291800 |
| `amber-200` | #fff4d4 | #331b00 |
| `amber-300` | #fff1c8 | #4f2900 |
| `amber-400` | #ffdd84 | #573200 |
| `amber-500` | #ffc85e | #6c4100 |
| `amber-600` | #ffaa00 | #e99c00 |
| `amber-700` | #ffb200 | #ffb200 |
| `amber-800` | #ff9900 | #ff9900 |
| `amber-900` | #a64f00 | #ff9900 |
| `amber-1000` | #541c00 | #fff3d9 |

### green

| Paso | Claro | Oscuro |
|---|---|---|
| `green-100` | #ecfdec | #00250a |
| `green-200` | #e5fce7 | #003110 |
| `green-300` | #d3fad1 | #003814 |
| `green-400` | #b9f5bc | #004616 |
| `green-500` | #82eb8d | #00661d |
| `green-600` | #4ce15e | #009431 |
| `green-700` | #28a948 | #00ab3e |
| `green-800` | #279141 | #009335 |
| `green-900` | #107d32 | #00ca52 |
| `green-1000` | #00370d | #daffe5 |

### teal

| Paso | Claro | Oscuro |
|---|---|---|
| `teal-100` | #dffffb | #00211b |
| `teal-200` | #ddfef6 | #002922 |
| `teal-300` | #ccf9f1 | #003b33 |
| `teal-400` | #b1f7ec | #003f35 |
| `teal-500` | #52f0db | #005f53 |
| `teal-600` | #00e2c4 | #009885 |
| `teal-700` | #00a694 | #00a794 |
| `teal-800` | #008d7d | #008d7d |
| `teal-900` | #007a6e | #00c9b5 |
| `teal-1000` | #003d34 | #cefff5 |

### purple

| Paso | Claro | Oscuro |
|---|---|---|
| `purple-100` | #f9f0ff | #290c33 |
| `purple-200` | #f9f1ff | #341142 |
| `purple-300` | #f5e8ff | #47185e |
| `purple-400` | #f1d9ff | #541a76 |
| `purple-500` | #dda9ff | #642290 |
| `purple-600` | #c77dff | #9440d5 |
| `purple-700` | #9f00f4 | #9440d5 |
| `purple-800` | #8400cd | #7d2bba |
| `purple-900` | #7c00c9 | #c472fb |
| `purple-1000` | #2e004d | #faedff |

### pink

| Paso | Claro | Oscuro |
|---|---|---|
| `pink-100` | #ffeaf5 | #310d1e |
| `pink-200` | #ffeaf2 | #420c25 |
| `pink-300` | #ffe0eb | #571032 |
| `pink-400` | #ffd5e1 | #5d0c34 |
| `pink-500` | #fdb3cc | #76063f |
| `pink-600` | #f97ea7 | #b90056 |
| `pink-700` | #f22782 | #f12b82 |
| `pink-800` | #e4106e | #e6006e |
| `pink-900` | #c41562 | #ff518d |
| `pink-1000` | #460523 | #ffeaf4 |

`brand` no está en la tabla porque no tiene valores fijos: se deriva de las tres variables de la app. Ver [Theming](/docs/theming).

## Tipografía

Las utilidades de Geist. `cn()` las entiende como tamaño de fuente, así que conviven con `text-gray-900`.

| Utilidad | Tamaño | Interlineado | Peso | Tracking |
|---|---|---|---|---|
| `text-heading-72` | 72px | 72px | 400 | -4.32px |
| `text-heading-64` | 64px | 64px | 400 | -3.84px |
| `text-heading-56` | 56px | 56px | 450 | -3.36px |
| `text-heading-48` | 48px | 56px | 450 | -2.88px |
| `text-heading-40` | 40px | 48px | 450 | -2.4px |
| `text-heading-32` | 32px | 40px | 500 | -1.28px |
| `text-heading-24` | 24px | 32px | 500 | -0.96px |
| `text-heading-20` | 20px | 26px | 550 | -0.4px |
| `text-heading-16` | 16px | 24px | 600 | -0.32px |
| `text-heading-14` | 14px | 20px | 600 | -0.28px |
| `text-button-16` | 16px | 20px | 500 | 0 |
| `text-button-14` | 14px | 20px | 500 | 0 |
| `text-button-12` | 12px | 16px | 500 | 0 |
| `text-label-20` | 20px | 32px | 400 | 0 |
| `text-label-18` | 18px | 20px | 400 | 0 |
| `text-label-16` | 16px | 20px | 400 | 0 |
| `text-label-14` | 14px | 20px | 400 | 0 |
| `text-label-13` | 13px | 16px | 400 | 0 |
| `text-label-12` | 12px | 16px | 400 | 0 |
| `text-label-14-mono` | 14px | 20px | 400 | 0 |
| `text-label-13-mono` | 13px | 20px | 400 | 0 |
| `text-label-12-mono` | 12px | 16px | 400 | 0 |
| `text-copy-24` | 24px | 36px | 400 | 0 |
| `text-copy-20` | 20px | 36px | 400 | 0 |
| `text-copy-18` | 18px | 28px | 400 | 0 |
| `text-copy-16` | 16px | 24px | 400 | 0 |
| `text-copy-14` | 14px | 20px | 400 | 0 |
| `text-copy-13` | 13px | 18px | 400 | 0 |
| `text-copy-14-mono` | 14px | 20px | 400 | 0 |
| `text-copy-13-mono` | 13px | 18px | 400 | 0 |

**Los `heading` llevan el peso corregido ópticamente**, no 600 fijo. Un mismo peso no se ve igual a 14px que a 64px: cuanto más grande el cuerpo, más gruesos se leen los trazos, y 600 a 64px sale plomizo. Los números salen de medir vercel.com (computed style, 2026-09-22); los pasos que no aparecían ahí se interpolan en la misma curva.

**No lo pises con `font-semibold`**: para eso está el paso de arriba de la escala. Y necesita Geist como fuente variable (rango `100 900`); con una estática los pesos intermedios se redondean y la corrección se pierde.

Cuándo usar cada familia:

| Familia | Para qué |
|---|---|
| `heading-*` | Títulos. Peso óptico, tracking negativo. |
| `copy-*` | Párrafos y texto corrido. Interlineado holgado. |
| `label-*` | Etiquetas, celdas, metadatos. Interlineado ajustado, peso 400. |
| `button-*` | Texto dentro de controles. Peso 500. |
| `*-mono` | Código, IDs, importes. Geist Mono. |

## Radios

| Utilidad | Valor |
|---|---|
| `rounded-xs` | 4px |
| `rounded-sm` | 6px |
| `rounded-md` | 6px |
| `rounded-lg` | 12px |
| `rounded-xl` | 12px |
| `rounded-2xl` | 16px |
| `rounded-3xl` | 16px |
| `rounded-4xl` | 9999px |

`rounded-md` (6px) es el radio del sistema: botones, inputs, ítems de menú. `rounded-xl` (12px) es el de las tarjetas. `rounded-full` solo en `Badge`, en avatares y en `Button shape="pill"`.

## Sombras

| Utilidad | Dónde |
|---|---|
| `shadow-tooltip` | Tooltip. |
| `shadow-menu` | DropdownMenu, Select, Combobox, Popover. |
| `shadow-modal` | Dialog, AlertDialog, Sheet. |

Las tres terminan en un anillo de 1px que en oscuro reemplaza al borde. No hay sombras decorativas: una sombra en este sistema significa «esto flota», y solo flotan los tres tipos de superficie de arriba.

## Foco y movimiento

| Utilidad | Dónde |
|---|---|
| `focus-visible:focus-ring` | Controles: botones, ítems de menú, tabs, toggles. Anillo de 2px en `brand-700` con hueco. |
| `focus:focus-border` | Campos: Input, Textarea, Select, Combobox. Tiñe el borde y agrega un halo. |
| `focus:focus-border-error` | Lo mismo, en rojo, cuando el campo tiene `aria-invalid`. |
| `transition-control` | 150 ms, `ease`, solo color / fondo / borde / sombra / opacidad. |
| `animate-skeleton` | El latido del `Skeleton`. |

Todas las animaciones pasan por `motion-reduce`, además del reset global del paquete.
