shadcn-ui/lint: an agent-first linter for Tailwind design systems
An agent-first linter for Tailwind design systems. Write design system rules that agents can verify.
At a glance
- What is it?
- It turns design system policy into lint errors that name the component, the rule and the fix. The README's own eval table suggests agents clear violations in one correction round, and the project ships as both an ESLint and an Oxlint plugin.
- Who is it for?
- Adopt it if you already run Tailwind v4 and want component-level usage policy that a coding agent can read and act on, especially when you do not own the components in question. Skip it if you need a general-purpose linting framework or you are not on Tailwind v4, since the README states that requirement and shadcn/ui is not required.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What shadcn-ui/lint actually enforces
The project describes itself as an agent-first linter for Tailwind design systems. The unit of enforcement is not a file or a function but a component contract: a pattern such as ^Button$ paired with the utility classes that component is allowed to carry. You write the policy, the linter checks JSX class names against it, and the error message is written for a reader that is about to edit the file.
That last part is the whole point. A TypeScript type can stop a padding prop from compiling, and the README shows exactly that with a Pick<React.CSSProperties, "margin" | "width"> example. The compiler error says padding does not exist on the type. It does not say to use a size variant instead, and it cannot, because the type has no knowledge of your variants. The linter's error does: the README's example diagnostic tells the reader that Button owns its spacing and points at sm, lg, margin, or gap on the parent.
The audience is teams running a Tailwind v4 design system whose components are consumed by coding agents, or by people who behave like them. The README states that shadcn/ui is not required, so the name is provenance rather than a dependency.
How contracts turn class names into diagnostics
Configuration lives under the shadcn/no-restyle rule, which takes an allow list of class categories plus a contracts array. Each contract has a pattern that matches a component name and its own allow and deny lists. In the README's Card example, ^CardTitle$ allows layout and typography but denies font-*, while ^CardContent$ allows layout and spacing. The result is that a title can grow to text-lg but not to md:font-bold, and content can take p-6 but not text-lg.
Categories are the abstraction layer. layout, spacing and typography appear in the examples; the README does not enumerate the full set, so treat that list as illustrative rather than complete. The second rule, shadcn/no-arbitrary-values, is orthogonal: it rejects bracket syntax like md:p-[13px] while leaving scale-based values such as p-6 and md:p-8 alone. Combining the two is how the README keeps spacing changes on the theme scale without freezing spacing entirely.
Messages are configurable per category. A message object keyed by spacing replaces the default text with something like "Use the size prop instead of padding." There is also a placeholder mechanism: for spacing errors, {{sizes}} expands to the component's available sizes, and the README says placeholders can draw on sizes, variants and file paths. That is where the design system data enters the message, and it is the mechanism behind the claim that suggestions come from your components rather than from a hardcoded list.
Installing @shadcn/lint and writing a first contract
The README's quickstart is a prompt, not a command list: it tells you to hand a coding agent the URL of SETUP.md and ask it to configure the project. That file exists at the repository root, and it is the authoritative install path. If you would rather do it yourself, the README points to a Get started section without reproducing the steps, so the exact package name and plugin registration should be read from SETUP.md rather than guessed.
The workspace itself is a pnpm monorepo. The root package.json is private, pins packageManager to pnpm@10.28.2, requires Node >=20.19 in engines, and builds the linter with a filtered script.
pnpm install
pnpm build
pnpm testThose three scripts are declared at the root: build runs the @shadcn/lint build, and test runs that build before the package's own tests. A separate release script chains typecheck, test, corpus:check and changeset publish, which tells you the maintainers treat the eval corpus as a release gate.
Once the plugin is registered, a first contract is a small edit to the rule options. This one allows only layout classes on Button and permits full width and vertical margin:
"shadcn/no-restyle": ["error", {
allow: ["layout"],
contracts: [
{ pattern: "^Button$", allow: ["w-full", "mt-*", "mb-*"] },
],
}]With that in place, the README's own example shows <Button className="mt-4 md:w-full" /> passing and <Button className="p-4 hover:rounded-full" /> failing. If your editor or agent surfaces nothing on the second line, the plugin is not loaded; the rule is inert until it is.
The eval numbers and what they do not cover
The README reports an eval over more than 150 task runs. In the per-model table, Sonnet 5, Haiku 4.5, Opus 5 and GPT 5.6 Terra each completed 8 of 8 tasks with zero errors after lint feedback, down from 69, 66, 42 and 117 errors respectively; GPT 5.6 Sol completed 6 of 8 with 98 before and 0 after. A separate claim states that in the Claude control runs, fixing violations with lint feedback cost 10% to 48% less than with rules alone. Methodology and further results live in docs/evals.md.
Read those numbers carefully. They measure whether an agent can clear violations when a linter is feeding it corrections, not whether the resulting UI is good, and not whether the rules you write are the right rules. The corpus is the maintainers' own; the release script runs corpus:check, so it is maintained, but it is still their tasks. A 0-after column also says nothing about how many correction rounds a harder task would need.
The honest limitation is different: this linter checks utility class strings. It cannot see whether a component was used in the right place, whether the copy is correct, or whether a variant exists that the contract forgot to mention. The README's own framing is that you decide what can change; that decision is yours, and a contract that is too permissive produces green output and a drifting design system.
ESLint, Oxlint, and where a linter beats a type
The README states availability for both ESLint and Oxlint, which matters if you have moved to Oxlint for speed and assumed you would lose plugin-based design rules. The comparison the project draws is against TypeScript, not against other linters. Expressing these policies in the type system, the README argues, takes complex types and forces component API changes; the linter keeps the API untouched and lets each project define its own contracts.
That distinction is real. A type is part of a component's published surface, so tightening it is a breaking change for every consumer. A lint rule is configuration, so two projects can ship the same Button with different contracts, and the README notes you can apply rules to components from third-party packages without forks or wrappers. If your problem is "we do not own this component but our agents keep restyling it," types cannot help you and this can.
The trade-off is enforcement location. Types fail the build; lint rules fail or warn depending on the severity you choose, and severity is yours to set. A team that sets the rule to "warn" has bought guidance, not a guarantee.
Maintenance, licence and upgrade cost
The repository is not archived and the last push was on 2026-09-14, one day before this writing, so it is being worked on right now. That is a snapshot, not a promise, and a project this young has no long deprecation record to inspect.
The release machinery is visible in the repository layout. There is a .changeset directory and a changeset script, and the release script runs typecheck, test, corpus:check and changeset publish in sequence. That is a conventional Changesets setup, which means version bumps and changelog entries are generated from committed changeset files; if you pin a version, the .changeset folder is where to look for what is coming. No releases were retrieved for this article, so there is no version history to cite.
The licence is MIT, stated in the repository's LICENSE file and in the metadata. MIT permits use, modification and redistribution with the licence and copyright notice retained; it provides no patent grant and no warranty. That is a general description of the licence text, not legal advice for your situation. The practical cost of adoption is configuration maintenance: contracts are code you own, and when a component gains a variant, the allow lists and the {{sizes}} placeholder output are only as current as your config.
Editorial conclusion
Adopt it if you already run Tailwind v4 and want component-level usage policy that a coding agent can read and act on, especially when you do not own the components in question. Skip it if you need a general-purpose linting framework or you are not on Tailwind v4, since the README states that requirement and shadcn/ui is not required. Before committing, read SETUP.md and docs/evals.md, and check the repository's package layout to confirm which plugins are published and how the contracts option is typed, because the README shows the rule configuration but not the full schema.
Frequently asked questions
What is a lint in coding?
A linter is a program that reads your source and reports patterns that break rules you have defined. shadcn-ui/lint applies that idea to Tailwind class names, checking JSX against component contracts and returning messages that name the fix.
Does shadcn-ui/lint require shadcn/ui components?
No. The README states it works with Tailwind v4 projects and that shadcn/ui is not required.
Which linters can run shadcn-ui/lint?
The README says the package is available for both ESLint and Oxlint.
How do I install shadcn-ui/lint?
The README's quickstart gives a prompt that asks a coding agent to read SETUP.md and set the project up, and points readers who prefer manual configuration to its Get started section. SETUP.md is the file to follow.
Community notes