@shadcn/lint: o linter que força seu agente de IA a respeitar seu design system em Tailwind CSS

@shadcn/lint é um linter open source de shadcn, para ESLint e Oxlint, que converte seu sistema de design em Tailwind CSS em erros que seu agente de IA lê e corrige sozinho. Cada erro explica o que quebrou e o que usar no lugar, a partir de seus próprios componentes, variantes e tema. O agente escreve p-[13px], executa npm run lint, recebe o tamanho correto e arruma seu próprio diff antes que um revisor humano veja.

É MIT, é publicado no npm como @shadcn/lint e funciona em qualquer projeto com Tailwind v4, quer você use shadcn/ui ou não.

O que é @shadcn/lint e o que faz um linter de sistema de design?

Um linter é uma ferramenta que analisa seu código sem executá-lo e reporta o que viola um conjunto de regras. É o mesmo que fazem ESLint ou Biome com erros de JavaScript. @shadcn/lint aplica essa ideia ao seu design system: não verifica se suas classes de Tailwind são válidas, mas como é permitido estilizar cada componente.

Você declara o que é permitido: que classes um Button aceita, se o padding deve ficar na escala de espaçamento, se cores soltas como bg-pink-500 são proibidas. O plugin reporta cada violação com uma correção tirada do seu próprio código.

shadcn o descreve como um agent-first linter. As regras são regras de lint normais, que rodam em seu editor e em sua CI como qualquer outra. O que muda é a mensagem de erro: está escrita para que um coding agent atue sobre ela, não para que um humano a leia de passagem.

Você precisa de shadcn/ui para usá-lo?

Não. O README diz que funciona com seus próprios componentes e seu próprio tema de Tailwind. Os projetos com shadcn/ui têm descobrimento automático de componentes e tema por meio de components.json. Se não usar shadcn/ui, você indica onde estão seus componentes com as configurações ui ou componentImports.

Por que um linter e não regras em AGENTS.md?

Porque um linter verifica o resultado, e seu erro diz o que fazer em seguida. O README ilustra isso com um Button que controla seu próprio padding. Você pode impor isso em TypeScript restringindo a prop style, mas o erro do compilador apenas diz que padding não é permitido. Não diz como dimensionar o botão.

Com @shadcn/lint, <Button className="p-4"> produz algo mais parecido com um comentário de revisão de código:

"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.

As regras escritas em prosa, em AGENTS.md ou em um prompt de sistema, dependem de o agente lembrá-las e aplicá-las. Um comando de lint é uma verificação que o agente executa e que não pode contornar com argumentos.

Que regras inclui?

Traz seis regras:

Regra O que detecta
no-restyle Alterar o estilo de um componente com className
no-raw-colors Cores soltas como bg-pink-500
no-arbitrary-values Valores arbitrários como p-[13px]
no-inline-styles Estilos inline e elementos <style>
no-unknown-classes Classes que Tailwind não consegue gerar, como rounded-huge
require-static-classes Classes que o linter não consegue ler, como `bg-${color}`

no-restyle faz a maior parte do trabalho; as demais fecham as vias de escape.

Como se instala com ESLint ou Oxlint?

A via mais rápida é que seu próprio agente o instale. O quickstart do README é um prompt:

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

SETUP.md indica ao agente que faça o seguinte:

  • detectar seu gerenciador de pacotes;
  • registrar o plugin no linter que você já usa, ou configurar Oxlint se não tiver nenhum;
  • preservar suas regras existentes.

Também lhe indica expressamente que não ative nenhuma regra. Decidir o que é permitido fica por sua conta.

Se preferir fazer manualmente, você precisa de Node.js 20.19 ou superior.

ESLint (9.30 ou 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 ou superior; sua API de plugins em JS ainda está em alpha):

npm install -D @shadcn/lint oxlint

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

Depois adicione o comando (eslint . ou oxlint) como script lint em package.json e coloque esta linha em AGENTS.md:

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

Essa linha fecha o ciclo: sem ela, o agente não sabe que o linter existe.

Como escrever regras para seu sistema de design em Tailwind CSS?

Comece com três. Juntas cobrem a deriva mais comum: componentes reestilizados de fora, espaçamento fora de escala e cores fixas no código.

1. Deixe um Button se mover, mas não mudar de forma. Um contrato define que classes um componente concreto aceita:

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

````<Button size="lg" className="mt-4 md:w-full" />` passa. `<Button className="p-4 hover:rounded-full" />` falha, e `<Button className="md:h-12 w-48" />` também.

**2. Permite alterar o espaçamento, mas apenas dentro da sua escala.** Combine um contrato que abre o espaçamento em `CardContent` com `no-arbitrary-values`:

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

p-6 md:p-8 passa. md:p-[13px] falha.

3. Escreva o erro com suas próprias palavras. As mensagens personalizadas aceitam placeholders, então o agente vê os tamanhos reais do seu componente:

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

Para um Button com tamanhos sm e lg, o erro se lê Use a Button size: sm, lg. Ative no-raw-colors ao lado e o agente irá buscar seus design tokens em vez de escrever bg-pink-500.

Os contratos são associados por nome de componente, então você pode aplicá-los a componentes de pacotes de terceiros sem fazer um fork nem envolvê-los.

Funciona de verdade com agentes de IA?

Segundo as avaliações do próprio shadcn, sim. São dados autorrelatados, publicados no repositório com metodologia e identificadores de cada execução, mas não reproduzidos de forma independente.

Em mais de 150 execuções de tarefas, quase todas chegaram a zero violações após uma única rodada de correção:

  • Em oito tarefas “de tentação”, que pedem estilos fora do sistema (um botão rosa, 13 px de padding, “faça destacar”), Sonnet 5 teve uma média de cerca de 70 violações por execução antes do feedback. Depois chegou a zero em 40 de 40 tarefas.
  • Nenhum modelo usou um comentário eslint-disable nem um estilo inline para conseguir isso. A correção típica foi adicionar uma variante ao componente, por exemplo variant="brand" em vez de uma cor hexadecimal fixa.

A comparação de custos é o mais interessante. Em execuções de controle, os agentes receberam as mesmas regras como texto e foram solicitados a revisar seu próprio trabalho. Comparado a isso, corrigir com os diagnósticos do linter custou menos nos três modelos:

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

Quanto menor o modelo, maior o ganho.

A própria página de avaliações lista seus limites: todas as execuções usaram uma única família de modelos, e o juiz que pontuou a fidelidade visual é dessa mesma família.

@shadcn/lint ou eslint-plugin-tailwindcss?

Resolhem problemas distintos. eslint-plugin-tailwindcss impõe higiene de Tailwind: ordem de classes, abreviações, classes contraditórias e nomes de classe personalizados. Seu README indica que o suporte para Tailwind v4 é parcial e está disponível apenas em um canal beta. @shadcn/lint foi feito para Tailwind v4 e impõe seu sistema de design: qual componente aceita qual classe e o que usar em seu lugar. Se seu problema é a ordem inconsistente das classes, você precisa do primeiro. Se seu problema é um agente que restaura o estilo do seu Button em cada PR, você precisa do segundo.

Funciona com Vue e Svelte?

Sim, desde a versão 0.2.0 (publicada em 22 de setembro de 2026), com uma limitação. Com ESLint, lê os templates de .vue e .svelte através de vue-eslint-parser e svelte-eslint-parser. Com Oxlint, analisa apenas os blocos <script>, porque Oxlint ainda não consegue ler esses templates. No momento da publicação desta nota, o README principal do repositório descreve apenas a instalação para React; o pacote npm e a pasta docs/ já cobrem os três frameworks.

O que não consegue detectar?

O que fica fora da marcação dos seus componentes. No teste de red-team do shadcn, foram tentadas sete formas de contornar as regras, e duas passaram:

  • uma classe CSS escrita diretamente em globals.css;
  • um token de tema novo criado para a ocasião.

A primeira é trabalho de um linter de CSS. A segunda está dentro do sistema por definição, porque o token vive no seu tema. Os tokens e variantes novos ainda precisam de revisão humana: passar no lint não aprova o design.

Se você já fornece ao seu agente um sistema de design legível por máquina, como Astryx do Meta ou um arquivo DESIGN.md, @shadcn/lint é a metade que o torna obrigatório. Essas ferramentas dizem ao agente como é o sistema; esta verifica se ele o seguiu.

Está pronto para produção?

Pode ser usado hoje, mas é jovem. Em 28 de setembro de 2026, o pacote vai pela versão 0.2.0, publicada pela primeira vez em 14 de setembro. Teve sete publicações no npm em oito dias e nenhuma versão etiquetada no GitHub. Espere mudanças nos nomes de opções e na sintaxe das regras antes da 1.0, e fixe a versão em package.json. O que perdura é a ideia: um sistema de design expresso como verificações que o agente executa, não como diretrizes que se pede ao agente lembrar.