@theme a variables de Figma
Extrae los tokens por defecto de Tailwind v4 (color, spacing, radius, tipografía…)
y los empaqueta en un JSON listo para crear variables nativas de Figma —
sin tocar tu theme.css ni tu proyecto.
Recordatorio temporal — quitar luego
Añadir al fichero de exportación los tipos fijos de border: border-2, border-4, border-8.
node_modules/tailwindcss/theme.cssDTCG (Design Tokens Community Group)@themePlugins → Variables JSON Import.
figma-tokens.json descargado arriba.
color/red/500, spacing/0_5, radius/lg...), igual que tu convención actual en la colección Tokens.
0_5, 1_5) porque así es como ya los tienes montados en tu colección Tokens — Figma no admite el punto en nombres de variable de forma fiable en todos los contextos.
em (relativo al tamaño de fuente); aquí se convierte a píxeles asumiendo una base de 16px, porque Figma no soporta unidades relativas en variables numéricas. Si tu cuerpo de texto no es 16px, ajusta esos valores a mano.
string) con el valor CSS tal cual. Matiz importante: los campos individuales de una sombra (X, Y, Blur, Spread, Color) sí se pueden enlazar a variables dentro de un Effect Style — lo que no existe es una variable única de tipo "sombra completa". Detalle paso a paso en la pestaña Referencia.
Estos tokens no tienen una variable "todo en uno" (Figma no tiene tipo de variable para sombra completa, curva de easing o animación) — pero sus campos individuales sí aceptan variables por separado. Esta sección es la receta para montarlos como Effect Styles (enlazando cada campo a tus primitivos), curvas de prototipado, o decisiones de Text Style — complementa las variables, no las sustituye.
En Figma: selecciona una capa → panel derecho → Effects → + → elige el tipo de efecto de la columna "Tipo de efecto en Figma" de cada tabla →
rellena los campos con los valores de la fila. En cada campo (X, Y, Blur, Spread, Color) pasa el cursor y pulsa el icono de variable para enlazarlo a un primitivo si tienes uno equivalente (el color, por ejemplo, a color/black) en vez de dejarlo fijo — así la sombra se actualiza si cambias el primitivo. Cuando esté montado, pulsa el icono de estilo (4 puntos) para guardarlo como Effect Style con el mismo nombre del token (p. ej. shadow/md).
Tipo de efecto en Figma: Drop Shadow (una capa de efecto por fila; si el token tiene varias capas, añade varios efectos del mismo tipo apilados sobre la misma capa).
| Token | Capa | X | Y | Blur | Spread | Color · Opacidad |
|---|---|---|---|---|---|---|
shadow/2xs | — | 0px | 1px | 0px | 0px | #000000 · 5% |
shadow/xs | — | 0px | 1px | 2px | 0px | #000000 · 5% |
shadow/sm | 1 / 2 | 0px | 1px | 3px | 0px | #000000 · 10% |
| 2 / 2 | 0px | 1px | 2px | -1px | #000000 · 10% | |
shadow/md | 1 / 2 | 0px | 4px | 6px | -1px | #000000 · 10% |
| 2 / 2 | 0px | 2px | 4px | -2px | #000000 · 10% | |
shadow/lg | 1 / 2 | 0px | 10px | 15px | -3px | #000000 · 10% |
| 2 / 2 | 0px | 4px | 6px | -4px | #000000 · 10% | |
shadow/xl | 1 / 2 | 0px | 20px | 25px | -5px | #000000 · 10% |
| 2 / 2 | 0px | 8px | 10px | -6px | #000000 · 10% | |
shadow/2xl | — | 0px | 25px | 50px | -12px | #000000 · 25% |
Tipo de efecto en Figma: Inner Shadow.
| Token | Capa | X | Y | Blur | Spread | Color · Opacidad |
|---|---|---|---|---|---|---|
insetShadow/2xs | — | 0px | 1px | 0px | 0px | #000000 · 5% |
insetShadow/xs | — | 0px | 1px | 1px | 0px | #000000 · 5% |
insetShadow/sm | — | 0px | 2px | 4px | 0px | #000000 · 5% |
Tipo de efecto en Figma: Drop Shadow.
| Token | Capa | X | Y | Blur | Color · Opacidad |
|---|---|---|---|---|---|
dropShadow/xs | — | 0px | 1px | 1px | #000000 · 5% |
dropShadow/sm | — | 0px | 1px | 2px | #000000 · 15% |
dropShadow/md | — | 0px | 3px | 3px | #000000 · 12% |
dropShadow/lg | — | 0px | 4px | 4px | #000000 · 15% |
dropShadow/xl | — | 0px | 9px | 7px | #000000 · 10% |
dropShadow/2xl | — | 0px | 25px | 25px | #000000 · 15% |
Tipo de efecto en Figma: Drop Shadow (una capa de efecto por fila; si el token tiene varias capas, añade varios efectos del mismo tipo apilados sobre la misma capa).
| Token | Capa | X | Y | Blur | Color · Opacidad |
|---|---|---|---|---|---|
textShadow/2xs | — | 0px | 1px | 0px | #000000 · 15% |
textShadow/xs | — | 0px | 1px | 1px | #000000 · 20% |
textShadow/sm | 1 / 3 | 0px | 1px | 0px | #000000 · 8% |
| 2 / 3 | 0px | 1px | 1px | #000000 · 8% | |
| 3 / 3 | 0px | 2px | 2px | #000000 · 8% | |
textShadow/md | 1 / 3 | 0px | 1px | 1px | #000000 · 10% |
| 2 / 3 | 0px | 1px | 2px | #000000 · 10% | |
| 3 / 3 | 0px | 2px | 4px | #000000 · 10% | |
textShadow/lg | 1 / 3 | 0px | 1px | 2px | #000000 · 10% |
| 2 / 3 | 0px | 3px | 2px | #000000 · 10% | |
| 3 / 3 | 0px | 4px | 8px | #000000 · 10% |
Estas curvas sí tienen un sitio nativo en Figma, pero no en Variables ni Styles — viven en el panel de prototipado.
Al crear una conexión de interacción, en el desplegable "Easing" elige Custom bezier e introduce los 4 números de cada cubic-bezier().
| Token | Valor CSS | 4 números para "Custom bezier" |
|---|---|---|
easing/in | cubic-bezier(0.4, 0, 1, 1) | 0.4 0 1 1 |
easing/out | cubic-bezier(0, 0, 0.2, 1) | 0 0 0.2 1 |
easing/in-out | cubic-bezier(0.4, 0, 0.2, 1) | 0.4 0 0.2 1 |
Estos presets combinan una animación CSS (@keyframes) con duración y easing. Figma no ejecuta keyframes de CSS,
así que no hay una forma de "importarlos" ni recrearlos exactamente con Smart Animate — sobre todo por el infinite (bucle), que Smart Animate no reproduce de forma nativa.
Guárdalos solo como documentación para quien implemente en código; si necesitas mostrar la idea en el prototipo, usa la duración como referencia aproximada.
| Token | Valor CSS | Duración | Easing equivalente |
|---|---|---|---|
animate/spin | spin 1s linear infinite | 1s | Linear |
animate/ping | ping 1s cubic-bezier(0,0,0.2,1) infinite | 1s | = easing/out |
animate/pulse | pulse 2s cubic-bezier(0.4,0,0.6,1) infinite | 2s | Custom: 0.4 0 0.6 1 |
animate/bounce | bounce 1s infinite | 1s | — |
El primitivo fontFamily/sans es la pila de fallback de Tailwind (ui-sans-serif, system-ui, sans-serif...), no el nombre de una fuente instalada —
Figma no puede enlazar eso a un campo de texto tal cual. Para usarlo de verdad: elige una fuente real que represente esa intención (la que uséis en producción),
instálala en Figma, y créala como propiedad de un Text Style. El primitivo sigue existiendo como documentación de qué renderiza el navegador;
la fuente real es la decisión visual encima de esa documentación — igual que un primitivo de color no te dice qué botón lo usa, esto no te dice qué fuente exacta dibujar, solo la intención.
| Token | Valor (documentación / fallback CSS) |
|---|---|
fontFamily/sans | ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji'... |
fontFamily/serif | ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif |
fontFamily/mono | ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas... |
Como comentamos: un título o un párrafo combina varias propiedades a la vez (tamaño, peso, interlineado, tracking), y una variable solo guarda un valor — por eso esta capa se resuelve con Text Styles, no con más variables ni con un componente. Punto de partida sugerido, construido enlazando los primitivos que ya tienes:
lineHeight tiene una entrada por cada tamaño de fontSize, ya en px — usa siempre la que comparte nombre con el tamaño que estés aplicando (fontSize/base → lineHeight/base), así el texto nunca queda ni apretado ni con huecos raros entre líneas.
Es una sugerencia de partida, no una regla fija: ajusta según cómo se vea en vuestros componentes reales.
Es el único tipo de estilo que aún no tenéis y no tiene alternativa en Variables — Figma no tiene un tipo "grid" bindable, así que se crea una vez como Grid Style y se aplica a los frames de cada breakpoint.
En Figma: selecciona el frame → panel derecho → Layout grid → + → tipo Columns → rellena columnas / margen / gutter de la fila correspondiente → guarda como estilo con el mismo nombre.
Propuesta de partida construida con vuestros primitivos spacing ya existentes (ajustad libremente, es un punto de partida, no una norma):
| Grid style | Ancho de referencia | Columnas | Margen | Gutter |
|---|---|---|---|---|
grid/base | < 640 (móvil, sin breakpoint) | 4 | spacing/4 (16px) | spacing/4 (16px) |
grid/sm | breakpoint/sm (640px) | 6 | spacing/6 (24px) | spacing/4 (16px) |
grid/md | breakpoint/md (768px) | 8 | spacing/8 (32px) | spacing/6 (24px) |
grid/lg | breakpoint/lg (1024px) | 12 | spacing/8 (32px) | spacing/6 (24px) |
grid/xl | breakpoint/xl (1280px) | 12 | spacing/12 (48px) | spacing/6 (24px) |
Los campos de margen y gutter dentro del Layout Grid sí aceptan bind a variable numérica (igual que en shadows) — enlázalos directamente a spacing/4, spacing/6, etc. en vez de escribir el número suelto, para que hereden cualquier cambio futuro de la escala.
El "ancho de referencia" no es un campo del grid en sí, es la anchura del frame en la que aplicas cada estilo (o el max-width si trabajáis con contenedores centrados — ahí sí entran vuestros primitivos container).
Estamos trabajando en ello
El camino inverso — pegar aquí un JSON exportado de Figma (Variables y Text Styles) y recibir el @theme de Tailwind listo para pegar en tu theme.css — está planeado pero aún no implementado.
Cuando tengas una exportación de prueba de tu plugin (Token Press, tokenHaus, Design Token Exporter...), tráela y lo construimos contra el formato real.
@theme desde cero, hay que evitar que ese proceso pise o borre los primitivos propios (marca, tipografía) que no vienen de Tailwind y que no se deben regenerar nunca automáticamente.
orange de marca: la misma lógica se aplica aquí a nivel de fichero completo, no solo a un color suelto.
color/primary apuntando a orange/500), el formato DTCG no exporta el hex final — exporta una referencia entre llaves:
{...}, sabe que es un alias — y en vez de "quemar" el hex, genera --color-primary: var(--color-orange-500);, para que el semántico se siga actualizando solo si cambia el primitivo.
{...} — algunos "aplanan" el valor final sin avisar y se pierde la relación. Hay que probarlo contra una exportación real antes de dar el algoritmo por bueno, no solo contra el estándar en teoría.
.css descargable con el semántico ya traducido a la sintaxis real de Tailwind, listo para pegar en theme.css:
@theme — no un fichero por variable, uno solo con todas las líneas juntas.
Estamos trabajando en ello
La finalidad de esta pestaña: coger pares de colores semánticos que ya tenéis pensados para texto/fondo
(por ejemplo color/text sobre color/surface, o color/danger sobre blanco) y calcular su ratio de contraste real,
marcando si pasan WCAG AA (mínimo legal habitual) y AAA — sin depender de comprobarlo a mano en Figma o con una extensión aparte cada vez que cambiéis un color.
Relevante para vosotros en concreto: una tienda online y contenido editorial legal caen dentro del alcance de la Ley europea de accesibilidad (obligatoria desde junio de 2025 para buena parte de los servicios digitales) — tener esto verificado en el propio flujo de tokens, antes de llegar a producción, evita sorpresas en una auditoría posterior.
Esto no sale de Tailwind ni tiene tabla que generar — son huecos habituales al montar un DS desde cero. Conviene decidirlos pronto: el coste de añadirlos después, con todo ya construido, es mayor.
color/primary solo no basta. Sin estados definidos, cada botón nuevo resuelve hover/focus/disabled con una opacidad improvisada distinta.
primary / warning, no después:
@theme — los z-index son valores arbitrarios. Sin una convención, aparecen z-[9999] sueltos en varios sitios del código.
color/primary, color/warning, y cualquier otro que varíe), no hace falta duplicar toda la colección de primitivos.
Primera versión — iremos ampliándola
Por ahora, la regla y la tabla de equivalencias que ya cubren la mayoría de casos. Pendiente de añadir: ejemplos con capturas reales del panel de Figma, casos de componentes completos, y cómo se traduce un modo de marca (multi-tema) al código.
La regla general: el nombre de la variable en Figma, sin la categoría delante, es lo que va después del guion en la clase de Tailwind.
Ves spacing/24 → escribes p-24. Ves color/red/500 → escribes bg-red-500.
| En Figma ves... | En código escribe... | Ejemplo |
|---|---|---|
color/red/500 | bg-, text-, border- + nombre completo | bg-red-500 |
spacing/24 | p-, m-, gap- + el número | p-24 → 96px |
radius/lg | rounded- + nombre | rounded-lg |
fontSize/base | text- + nombre | text-base |
fontWeight/bold | font- + nombre | font-bold |
lineHeight/base | nada — text-base solo ya lo aplica | text-base → 24px automático |
letterSpacing/wide | tracking- + nombre | tracking-wide |
container/lg | max-w- + nombre | max-w-lg |
blur/md | blur- + nombre | blur-md |
aspectRatio/video | aspect- + nombre | aspect-video |
easing/out | ease- + nombre | ease-out |
shadow/md | shadow- + nombre | shadow-md |
animate/spin | animate- + nombre | animate-spin |
transition/duration | duration- + el número | duration-150 |
breakpoint/md | prefijo de variante, no clase suelta | md:flex |
lineHeight, es la excepción a la regla general: el número de nuestro token es px absoluto, pero leading-{número} en Tailwind lo multiplica por tu --spacing (4px) — leading-24 no da 24px, da 96px.
Dos formas correctas de aplicarlo: si usas fontSize y lineHeight de la misma clave (p. ej. base + base), no escribas nada — text-base solo ya trae ese interlineado por defecto. Si necesitas forzarlo distinto, usa corchetes: leading-[24px], nunca leading-24 sin corchetes.