@shadcn/lint: el linter que obliga a tu agente de IA a respetar tu design system en Tailwind CSS

@shadcn/lint es un linter open source de shadcn, para ESLint y Oxlint, que convierte tu sistema de diseño en Tailwind CSS en errores que tu agente de IA lee y corrige solo. Cada error explica qué se rompió y qué usar en su lugar, a partir de tus propios componentes, variantes y tema. El agente escribe p-[13px], ejecuta npm run lint, recibe el tamaño correcto y arregla su propio diff antes de que un revisor humano lo vea.

Es MIT, se publica en npm como @shadcn/lint y funciona en cualquier proyecto con Tailwind v4, uses o no shadcn/ui.

¿Qué es @shadcn/lint y qué hace un linter de sistema de diseño?

Un linter es una herramienta que analiza tu código sin ejecutarlo y reporta lo que viola un conjunto de reglas. Es lo mismo que hacen ESLint o Biome con errores de JavaScript. @shadcn/lint aplica esa idea a tu design system: no revisa si tus clases de Tailwind son válidas, sino cómo se permite dar estilo a cada componente.

Tú declaras qué está permitido: qué clases acepta un Button, si el padding debe quedarse en la escala de espaciado, si los colores sueltos como bg-pink-500 están prohibidos. El plugin reporta cada violación con una corrección tomada de tu propio código.

shadcn lo describe como un agent-first linter. Las reglas son reglas de lint normales, que corren en tu editor y en tu CI como cualquier otra. Lo que cambia es el mensaje de error: está escrito para que un coding agent actúe sobre él, no para que un humano lo lea de pasada.

¿Necesitas shadcn/ui para usarlo?

No. El README dice que funciona con tus propios componentes y tu propio tema de Tailwind. Los proyectos con shadcn/ui tienen descubrimiento automático de componentes y tema mediante components.json. Si no usas shadcn/ui, indicas dónde están tus componentes con los ajustes ui o componentImports.

¿Por qué un linter y no reglas en AGENTS.md?

Porque un linter comprueba el resultado, y su error dice qué hacer a continuación. El README lo ilustra con un Button que controla su propio padding. Puedes imponer eso en TypeScript restringiendo la prop style, pero el error del compilador solo dice que padding no está permitido. No dice cómo darle tamaño al botón.

Con @shadcn/lint, <Button className="p-4"> produce algo más parecido a un comentario de code review:

"p-4" is not allowed on <Button>: <Button> owns its spacing.
Use a size (sm, lg), or margin here or gap on the parent for space around it.
Add a size in components/ui/button.tsx only if the design explicitly calls for one.

Las reglas escritas en prosa, en AGENTS.md o en un prompt de sistema, dependen de que el agente las recuerde y las aplique. Un comando de lint es una verificación que el agente ejecuta y que no puede esquivar con argumentos.

¿Qué reglas incluye?

Trae seis reglas:

Regla Qué detecta
no-restyle Cambiar el estilo de un componente con className
no-raw-colors Colores sueltos como bg-pink-500
no-arbitrary-values Valores arbitrarios como p-[13px]
no-inline-styles Estilos inline y elementos <style>
no-unknown-classes Clases que Tailwind no puede generar, como rounded-huge
require-static-classes Clases que el linter no puede leer, como `bg-${color}`

no-restyle hace la mayor parte del trabajo; las demás cierran las vías de escape.

¿Cómo se instala con ESLint u Oxlint?

La vía más rápida es que lo instale tu propio agente. El quickstart del README es un prompt:

Read https://github.com/shadcn-ui/lint/blob/a89d04792f340bcea26af2d539a9eec7285fcc77/SETUP.md
and set up @shadcn/lint in this project.

SETUP.md le indica al agente que haga lo siguiente:

  • detectar tu gestor de paquetes;
  • registrar el plugin en el linter que ya usas, o configurar Oxlint si no tienes ninguno;
  • conservar tus reglas existentes.

También le indica expresamente que no active ninguna regla. Decidir qué está permitido queda en tus manos.

Si prefieres hacerlo a mano, necesitas Node.js 20.19 o superior.

ESLint (9.30 o superior):

npm install -D @shadcn/lint eslint @typescript-eslint/parser

// eslint.config.mjs
import { plugin as shadcn } from "@shadcn/lint"
import tsParser from "@typescript-eslint/parser"
import { defineConfig } from "eslint/config"

export default defineConfig([
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
    plugins: { shadcn },
    rules: {
      "shadcn/no-arbitrary-values": "error",
    },
  },
])

Oxlint (1.80 o superior; su API de plugins en JS todavía está en alpha):

npm install -D @shadcn/lint oxlint

{
  "jsPlugins": ["@shadcn/lint"],
  "rules": {
    "shadcn/no-arbitrary-values": "error"
  }
}

Después agrega el comando (eslint . o oxlint) como script lint en package.json y pon esta línea en AGENTS.md:

After making changes, run `npm run lint` and fix all errors.

Esa línea cierra el ciclo: sin ella, el agente no sabe que el linter existe.

¿Cómo escribir reglas para tu sistema de diseño en Tailwind CSS?

Empieza con tres. Juntas cubren la deriva más común: componentes reestilizados desde fuera, espaciado fuera de escala y colores fijos en el código.

1. Deja que un Button se mueva, pero no que cambie de forma. Un contrato define qué clases acepta un componente concreto:

"shadcn/no-restyle": ["error", {
  allow: ["layout"],
  contracts: [
    { pattern: "^Button$", allow: ["w-full", "mt-*", "mb-*"] },
  ],
}]

<Button size="lg" className="mt-4 md:w-full" /> pasa. <Button className="p-4 hover:rounded-full" /> falla, y <Button className="md:h-12 w-48" /> también.

2. Permite cambiar el espaciado, pero solo dentro de tu escala. Combina un contrato que abre el espaciado en CardContent con no-arbitrary-values:

"shadcn/no-restyle": ["error", {
  allow: ["layout"],
  contracts: [
    { pattern: "^CardContent$", allow: ["layout", "spacing"] },
  ],
}],
"shadcn/no-arbitrary-values": "error",

p-6 md:p-8 pasa. md:p-[13px] falla.

3. Escribe el error con tus propias palabras. Los mensajes personalizados aceptan placeholders, así que el agente ve los tamaños reales de tu componente:

"shadcn/no-restyle": ["error", {
  allow: ["layout"],
  message: {
    spacing: "Use a {{component}} size: {{sizes}}.",
  },
}]

Para un Button con tamaños sm y lg, el error se lee Use a Button size: sm, lg. Activa no-raw-colors a su lado y el agente irá a buscar tus design tokens en lugar de escribir bg-pink-500.

Los contratos se asocian por nombre de componente, así que puedes aplicarlos a componentes de paquetes de terceros sin hacer un fork ni envolverlos.

¿Funciona de verdad con agentes de IA?

Según las evaluaciones del propio shadcn, sí. Son datos autorreportados, publicados en el repositorio con metodología e identificadores de cada ejecución, pero no reproducidos de forma independiente.

En más de 150 ejecuciones de tareas, casi todas llegaron a cero violaciones tras una sola ronda de corrección:

  • En ocho tareas “de tentación”, que piden estilos fuera del sistema (un botón rosa, 13 px de padding, “hazlo destacar”), Sonnet 5 promedió unas 70 violaciones por ejecución antes del feedback. Después llegó a cero en 40 de 40 tareas.
  • Ningún modelo usó un comentario eslint-disable ni un estilo inline para lograrlo. La corrección típica fue añadir una variante al componente, por ejemplo variant="brand" en lugar de un color hexadecimal fijo.

La comparación de costos es lo más interesante. En ejecuciones de control, los agentes recibieron las mismas reglas como texto y se les pidió revisar su propio trabajo. Frente a eso, corregir con los diagnósticos del linter costó menos en los tres modelos:

  • Haiku 4.5: de 1,41 a 0,74 dólares
  • Sonnet 5: de 3,57 a 2,47 dólares
  • Opus 5: de 4,35 a 3,93 dólares

Cuanto más pequeño el modelo, mayor la ganancia.

La propia página de evaluaciones enumera sus límites: todas las ejecuciones usaron una sola familia de modelos, y el juez que puntuó la fidelidad visual es de esa misma familia.

¿@shadcn/lint o eslint-plugin-tailwindcss?

Resuelven problemas distintos. eslint-plugin-tailwindcss impone higiene de Tailwind: orden de clases, abreviaturas, clases contradictorias y nombres de clase personalizados. Su README indica que el soporte para Tailwind v4 es parcial y solo está disponible en un canal beta. @shadcn/lint está hecho para Tailwind v4 e impone tu sistema de diseño: qué componente acepta qué clase y qué usar en su lugar. Si tu problema es el orden inconsistente de las clases, necesitas el primero. Si tu problema es un agente que reestiliza tu Button en cada PR, necesitas el segundo.

¿Funciona con Vue y Svelte?

Sí, desde la versión 0.2.0 (publicada el 22 de septiembre de 2026), con un límite. Con ESLint lee las plantillas de .vue y .svelte mediante vue-eslint-parser y svelte-eslint-parser. Con Oxlint solo analiza los bloques <script>, porque Oxlint todavía no puede leer esas plantillas. Al momento de publicar esta nota, el README principal del repositorio solo describe la instalación para React; el paquete de npm y la carpeta docs/ ya cubren los tres frameworks.

¿Qué no puede detectar?

Lo que queda fuera del marcado de tus componentes. En la prueba de red-team de shadcn se intentaron siete formas de saltarse las reglas, y dos pasaron:

  • una clase CSS escrita directamente en globals.css;
  • un token de tema nuevo creado para la ocasión.

La primera es trabajo de un linter de CSS. La segunda está dentro del sistema por definición, porque el token vive en tu tema. Los tokens y variantes nuevos siguen necesitando revisión humana: que el lint pase no aprueba el diseño.

Si ya le das a tu agente un sistema de diseño legible por máquina, como Astryx de Meta o un archivo DESIGN.md, @shadcn/lint es la mitad que lo hace cumplir. Esas herramientas le dicen al agente cómo es el sistema; esta comprueba si le hizo caso.

¿Está listo para producción?

Se puede usar hoy, pero es joven. Al 28 de septiembre de 2026, el paquete va por la versión 0.2.0, publicada por primera vez el 14 de septiembre. Lleva siete publicaciones en npm en ocho días y ningún release etiquetado en GitHub. Espera cambios en los nombres de opciones y en la sintaxis de las reglas antes de la 1.0, y fija la versión en package.json. Lo que perdura es la idea: un sistema de diseño expresado como verificaciones que el agente ejecuta, no como pautas que se le pide recordar.