Back to Devoured - September 15, 2026
DEVOURED
@shadcn/lint (GitHub Repo)

@shadcn/lint (GitHub Repo)

Tech GitHub
@shadcn/lint is a new linter designed to enforce design system constraints on autonomous coding agents using custom rules and Tailwind configuration.
What: The tool allows developers to define explicit design 'contracts' that prevent coding agents from arbitrarily modifying component styles, like padding or color, without triggering corrective feedback.
Why it matters: As agents increasingly write UI code, standard TypeScript types often lack the semantic clarity to guide them properly; this linter fills that gap with domain-specific feedback for design systems.
Takeaway: Run `npx oxlint` or `eslint .` after setting up @shadcn/lint to enforce your design system rules on AI-generated code.
Deep dive
  • Works with both ESLint and Oxlint, supporting Tailwind v4 projects.
  • Allows defining granular rules for components, variants, and theme-based spacing.
  • Provides actionable error messages that tell agents exactly how to fix a violation based on your design system components.
  • Reduces the cost of agent-led UI tasks by ensuring they don't break design standards in the first round of generation.
  • Enables cross-project rule sharing through shared configuration, which is especially useful in monorepos.
Decoder
  • Linter: A static analysis tool used to flag programming errors, bugs, stylistic errors, and suspicious constructs in source code.
Original article

@shadcn/lint

Write design system rules that agents can verify.

@shadcn/lint is an agent-first linter for Tailwind design systems.

You define what’s allowed. When an agent breaks a rule, the error explains what’s wrong and suggests a fix based on your components, variants, and theme.

Works with your existing design system. No rewrite required.

@shadcn/lint works with Tailwind v4 projects (shadcn/ui not required). Available for both ESLint and Oxlint.

Quickstart

Give your coding agent this prompt:

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

Once installed, choose your rules and configure what’s allowed in your design system.

TypeScript vs @shadcn/lint

Take a Button that allows margin and width, but controls its own padding. You can enforce that with types by limiting its style prop to Pick<React.CSSProperties, "margin" | "width">.

<Button style={{ padding: 16 }}>Submit</Button>

TS2353: Object literal may only specify known properties, and 'padding' does not exist in type 'Pick<CSSProperties, "margin" | "width">'.

The rule works. But this error only tells the agent that padding is not allowed. It doesn’t tell it how to size the Button.

With @shadcn/lint, the same rule comes with guidance from your design system:

<Button className="p-4">Submit</Button>

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

You decide what can change

Expressing these policies in TypeScript can take complex types. With @shadcn/lint, you configure them without changing your component API.

Here are some examples.

Allow spacing with margin. Allow full width. Keep size and shape in the Button.

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

// Allowed: use a size and let the page control placement and full width.
<Button size="lg" className="mt-4 md:w-full" />

// Error: you are not allowed to change padding and shape.
<Button className="p-4 hover:rounded-full" />

// Error: you are not allowed to set a custom height or fixed width.
<Button className="md:h-12 w-48" />

Give each part of a component its own rules.

Let Card titles change typography, but keep their font family and weight. Let Card content change spacing, but keep its typography.

"shadcn/no-restyle": ["error", {
  allow: ["layout"],
  contracts: [
    {
      pattern: "^CardTitle$",
      allow: ["layout", "typography"],
      deny: ["font-*"],
    },
    { pattern: "^CardContent$", allow: ["layout", "spacing"] },
  ],
}]

// Allowed: titles can change text size; content can change padding.
<CardTitle className="text-lg" />
<CardContent className="p-6" />

// Error: you are not allowed to change the title’s font weight.
<CardTitle className="md:font-bold" />

// Error: you are not allowed to change the content’s typography.
<CardContent className="text-lg" />

Allow spacing changes. Require theme values.

Opening up spacing doesn’t have to mean allowing arbitrary values. Combine rules to let Card content change padding while keeping it on your theme’s spacing scale.

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

// Allowed: padding uses the theme’s spacing scale.
<CardContent className="p-6 md:p-8" />

// Error: you are not allowed to use an arbitrary padding value.
<CardContent className="md:p-[13px]" />

Built for agents

We built @shadcn/lint for agents that write UI. The errors tell them what broke, what to use instead, and where to find it. Suggestions come from your components, variants, and theme.

It works

We tested these rules with coding agents across more than 150 task runs. Almost every task reached zero violations in one correction round.

Model Completed tasks Errors before Errors after
Sonnet 5 8/8 69 0
Haiku 4.5 8/8 66 0
Opus 5 8/8 42 0
GPT 5.6 Terra 8/8 117 0
GPT 5.6 Sol 6/8 98 0

It is cheaper

In the Claude control runs, fixing violations with lint feedback cost 10% to 48% less than with rules alone.

Why a linter?

A linter is programmable. You can write rules for your design system without changing your components.

You define what’s allowed and what to use instead. Agents run your lint command to check their work.

  • Ship the same components with different rules. Each project can define its own contracts without changing the component code.
  • Use components you don’t own. Apply rules to components from third-party packages. No forks. No wrappers.
  • Share rules across projects. Keep a shared configuration for your design system and let projects add their own rules.

Programmable

Custom messages

You can write custom error messages that tell agents what to do.

"shadcn/no-restyle": ["error", {
  allow: ["layout"],
  message: {
    spacing: "Use the size prop instead of padding.",
  },
}]

Placeholders

Use your component’s sizes, variants, and file paths in error messages. For spacing errors, {{sizes}} lists the available sizes.

Contracts

Give each component its own rules. For example, let pages change a card title's typography.

Get started

Choose Oxlint or ESLint. The examples below enable no-restyle and allow layout classes such as mt-4 and w-full.

Oxlint

npm install -D @shadcn/lint oxlint

Create .oxlintrc.json:

{
  "jsPlugins": ["@shadcn/lint"],
  "rules": {
    "shadcn/no-restyle": [
      "error",
      {
        "allow": ["layout"]
      }
    ]
  }
}

ESLint

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

Create 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-restyle": [
        "error",
        {
          allow: ["layout"],
        },
      ],
    },
  },
])

Rules

Rule What it catches
no-restyle Restyling a component with className.
no-raw-colors Raw colors such as bg-pink-500.
no-arbitrary-values Arbitrary values such as p-[13px].
no-inline-styles Inline styles and <style> elements.
no-unknown-classes Classes Tailwind cannot generate, such as rounded-huge.
require-static-classes Component classes the linter cannot read, such as `bg-${color}`.

Settings

Use settings.shadcn to configure component imports, class functions, and guidance shared across rules.

You don’t need shadcn/ui to use @shadcn/lint. It works with your own Tailwind components and theme.

Setting What it does
ui Recognizes component imports by prefix.
componentImports Recognizes component imports using regex patterns.
ignoreImports Skips component recognition for imports matching these regex patterns.
mergeFunctions Adds functions whose arguments contain classes.
variantFunctions Adds functions whose object values contain classes.
note Appends your text to every rule's error or warning.
Explore all 47 articles in today's digest

Fresh dev news, AI-digested daily. Never miss a trend at devoured.fyi