shadcn Just Made Vibe Coding Less Painful

summarized

TLDR

shadcn/lint is an agent-first linter that tells AI agents which of your own components, variants, or theme tokens to use when they break a design rule, instead of just rejecting the code. It targets design-system drift by giving agents a concrete fix, like "use size LG" or "bg-primary," and its own evals show most tasks reach zero violations after one correction round. Worth trying if you have a Tailwind V4 design system and agents editing UI regularly.

Key points

shadcn/lint ships six rules: no restyle, no raw colors, no arbitrary values, no inline styles, no unknown classes, and require static classes.

Contracts give each component its own permissions, like allowing layout but denying font overrides for card titles.

Existing Tailwind V4 setups can run it via ESLint or Oxlint in React, Svelte, and Vue without needing shadcn/ui.

shadcn's team published evals over 150 task runs, with most tasks reaching zero violations after a single correction round.

The correction cost with diagnostics was 10-48% lower for Claude models, but sample sizes were small and a Claude model did the judging.

Tools mentioned

Techniques

  • Agent-first linting with actionable fix suggestions
  • Contract-based per-component permissions
  • Feedback loop where agents receive lint diagnostics
  • Rule-based drift detection for design systems
Transcript (captions)

0:00 You ask an AI agent to make the delete button stand out a bit more. What comes back is a button with class name P4, BG pink 500, and rounded full. Sure, it stands out, but it's pink, fully round,

0:11 and padded by hand, and none of that came from your design system. One edit like that is easy to spot. 20 of them spread across a code base aren't. Every screen drifts a little further from the

0:21 system you built, and each change looked perfectly reasonable in its own diff. Your usual defense is a line in your rules file asking the agent to follow the design system. All that line does is

0:32 ask the agent to review its own work. Nothing checks it, and nothing tells it the right replacement. So, the agent marks its own homework, and it's a generous marker. Shad/Lint flips that.

0:43 Its repo calls it an agent-first linter. You write down what's allowed, and when the agent breaks a rule, the error tells it which of your components, variants, or theme colors to use instead.

0:53 Suggesting a fix is what sets it apart. So, picture an inspector standing at the door of your button component. Every class the agent writes has to get past this inspector before it's accepted.

1:03 Your agent walks up and hands over its line. Button, class name P4, BG pink 500, rounded full. Stamp in hand, the inspector starts with the first class, P4. Rejected. Back comes the slip with a

1:15 note clipped to it, and that note's written from your own button.tsx, the component file your project already has. Word for word, P4 is not allowed on button. Button owns its spacing. Use a

1:26 size SM or LG or margin here or gap on the parent for space around it. Then there's one more line. Add a size in components/UI/button.tsx only if the design explicitly calls for

1:38 one. So, the note even tells the agent when changing the component itself is fair game. Mind you, that's guidance. You won't find a lock on that file. Why bother with all that extra text? A rule

1:49 that only rejects padding still works, as the read me points out. But in its words, it doesn't tell the agent how to size the button. So, the agent's left guessing, and guessing is how you got P4

1:59 in the first place. Two more stamps come down the same way. BG pink 500 pulls a color straight from Tailwind's palette. Rounded full overrides the shape the button already defines. On a button, the

2:10 docs flag padding, color, and radius overrides like these. Notes read, "The agent tries again." No class name fighting the component this time, just size LG and variant

2:20 destructive. Both are the button's own options, so the inspector waves it through, and the button snaps back into the system. Seeing what still passes helps too. Class name MT4 W full on a

2:31 button is fine. Margin and width decide where the button sits on the page, not what it looks like, so the inspector lets them in. Everything here leans on one split. Placement belongs to the page

2:41 around the component. Appearance belongs to the component and its variants. When an agent reaches into the second one through class name, it gets a note. Other rules hand back the same kind of

2:51 note. Say the agent writes P minus beer there ends pun rent. Tailwind calls that an arbitrary value, any number you like in square brackets. Now the error reads, "P minus beer there ends pun rent pun

3:03 rent pun rent pun rent beer there ends han minus pun rent ear there ends end." Now the error reads, "Pun minus pun rent pun rent beer there ends end." Use beer 3.25 instead, same value on the scale.

3:16 So, 13 pixels was on the scale the whole time under a less exciting name. Nothing on the page moves a pixel, and the class now sits on Tailwind's spacing scale instead of beside it. Raw colors get

3:27 swapped for theme tokens. In the no raw colors docs, an account settings div with BG pink 500 comes back as BG primary. The docs fix also moves the text onto a theme token, text muted

3:40 foreground. Your theme's names replace Tailwind's palette names. BG primary points at whatever your theme says primary is. BG pink 500 points at pink, and it'll you'll pointing at pink long

3:51 after your brand has moved on to something else. Now zoom out. You've met three inspectors so far. Shadcn/lint ship six rules, so picture six of them in a row, each one watching for a

4:02 different kind of drift. No restyle is the one we've been watching, restyling a component through class name. No raw colors catches pilot colors like bg-pink-500.

4:11 No arbitrary values catches arbitrary values like p-mint-a-thords. Another rule covers inline styles, which skip your classes altogether. Then there's no unknown classes, for

4:22 classes Tailwind can't generate at all. Rounded huge, for instance, reads great in a diff and does nothing on screen. Rule six, require static classes, flags classes built from a variable.

4:33 No restyle can't read a class that's pieced together at runtime, and that leaves a hole in coverage. If a class is built at runtime, the linter can't see it until you write it out in full.

4:43 Contracts make the rules sharper by giving each component its own permissions. Instead of one policy for everything, you decide what each component is

4:51 allowed to change. Take the readme's example, card title. Layout and typography classes are allowed, and anything starting with font is denied. So an agent can position a title and

5:01 adjust its text, but it can't override the font the card already chose. What you've got now is a design decision a program can check. A rule that usually lives in someone's head sits in config,

5:11 right where the agent will run into it. Getting it into a code base doesn't need a rewrite, according to the project. Existing Tailwind V4 setups can run it through ESLint or Oxlint in React,

5:22 Svelte, and Vue. You don't need Shadcn/ui to use it. If you do use Shadcn/ui, it discovers your components and theme automatically through components.json,

5:33 which makes that the easiest place to start. In the project's evals, most runs fed the diagnostics straight back to the agent. So whatever you wire up, make sure the agent actually sees the lint

5:43 output, not just your CI dashboard. One boundary to keep in mind, shadcn/lint checks the classes and styles in the files it lints. An agent can still

5:53 wander into files you never mentioned and the linter won't stop it. But if those files are part of the lint run, a pink class there still gets flagged with a note pointing at the fix. Does the

6:03 note actually change what agents do? shadcn's team published evals, over 150 task runs, and most tasks reached zero violations after a single correction round. Their results table puts it

6:15 bluntly, 69 errors before correction, zero after the correction rounds. That's their test, not ours. The part I'd look at hardest is the control, because it isolates the notes. Same starting

6:26 output, same rules text, same three rounds, just no diagnostics. The agent's only asked to review its own file. Compared with that, the runs with diagnostics converged in one round for

6:37 all three Claude models, and the correction cost came out 10 to 48% lower. Now the fine print. Per model run counts are small, two runs for Opus 5 and a

6:47 single control run per model. Two runs is technically a sample. The cost claim only covers Claude models, and a Claude family model did the judging. The rules and prompts also change between

6:57 measurements. GPT models show up in the table, just not in the cost claim. Put together, I'd trust the direction more than the exact percentages. Even without the table, the

7:07 mechanism holds up. An error that names size LG gives the agent something to do. A line asking it to follow the design system doesn't. It'd be easy to oversell this as a fix

7:17 for Tailwind in general. I'd put it narrower. What it targets is design system drift, the gap between everything Tailwind lets you write and the smaller set your system actually meant. My call,

7:28 if you've got a design system on Tailwind V4 and agents touching your UI every week, try shadcn/lint. Start with no restyle on button, since that's where the docs own examples live,

7:39 and watch what the agent does with the notes. If your app is mostly one-off pages with few shared components, there's less for the inspectors to guard and I'd wait

7:47 until you've built a system that needs protecting. So, which of your components should get a contract and what should each one be allowed to change?

Frontier News · by Hyperjump Technology