Around 2013 you could guess a startup's stack from across the room. Fixed navbar. A big gray hero block called a jumbotron. Three columns with round icons. A blue button with a faint gradient. Bootstrap made a large part of the web look like one company built it.
It deserved to win. It gave teams a responsive grid when responsive was hard, smoothed over browsers that disagreed about everything, and let a backend developer ship something presentable by Friday. For prototypes and internal tools it was close to perfect.
The trouble started when the prototype became the product. Teams overrode the defaults one selector at a time. .btn-primary got a custom color, then a custom radius, then an !important to win a specificity war nobody remembered starting. Themes came from marketplaces and went on top. When Bootstrap 4 swapped floats for flexbox and LESS for Sass, plenty of teams found out their "design system" was a pile of overrides against a dependency they could no longer upgrade.
They hadn't built a system. They had rented one and redecorated.
The same move, with better tools
shadcn/ui looks like it learned every one of those lessons. It isn't a dependency: a CLI copies the component source straight into your repo, so you own the code. It sits on accessible headless primitives, styles with Tailwind and CSS variables, and the defaults are genuinely tasteful. Most of Bootstrap's failure modes don't apply.
It also has an accelerant Bootstrap never had. shadcn is what AI tools reach for. Ask v0, Lovable, Bolt, or a coding agent for a dashboard and you will very likely get shadcn: zinc grays, a half-rem radius, cards with a hairline border, Geist or Inter. The bordered card grid is the new jumbotron. This time the sameness ships automatically, at scale, by default.
Owning the files is not owning the decisions
shadcn is refreshingly honest about what it is:
"This is not a component library. It is how you build your component library."
shadcn/ui documentation
The fallacy belongs to the teams, not the tool. They run init, pick a base color, nudge the radius, and announce a design system. What they have is a well-made starting point with their logo on it.
A design system isn't a folder of components. It is the decisions behind them, written down. Why primary is this color, and where it is not allowed. How dense a table should be in your product. What separates an error from a warning from the blocked state only your domain has. It is the shared language between design and engineering, one token source that Figma and the repo both read, a contribution model, and someone accountable for saying no. shadcn ships none of that, and doesn't pretend to.
When I audit a system, my first question is where those decisions are written down. On a fresh shadcn start, the honest answer is usually: nowhere yet. That's fine in month one. In year two, you're paying the tax.
Copy-paste ownership has its own bill. Every component you add is code you now maintain without an upstream. When Tailwind moved to v4, every copied component became a migration ticket nobody owned. When three product teams each add the same Button and tweak it locally, you have three Buttons and no way to tell which one is canonical.
Bootstrap's failure was overrides against a dependency. shadcn's is forks without a parent. Different shape, same drift.
What's actually different this time
To be fair, the floor is much higher. Accessible primitives, real keyboard support, owned source, and CSS variables make a shadcn start far easier to grow out of than Bootstrap ever was. That is an argument for using it as scaffolding. It is not an argument for leaving the scaffolding up.
| Bootstrap era (~2013) | shadcn era (~2026) | |
|---|---|---|
| Distribution | Package or CDN dependency | Source copied into your repo |
| Customization | Override classes and variables | Edit source, theme with CSS variables |
| Accessibility | Uneven, mostly manual | Strong, via headless primitives |
| The look everyone knows | Jumbotron, three icon columns | Bordered cards, zinc grays |
| How it spreads | Tutorials, theme marketplaces | AI generators and coding agents |
| Long-term failure mode | Overrides that block upgrades | Local forks with no canonical source |
Why the custom system is worth it
A custom system is slower. It takes months to pay back, and it needs people who own it. This is what that buys you.
- Your brand is a set of decisions, not a palette. A theme changes colors. A system encodes how your product behaves: density, hierarchy, motion, voice. When everyone builds from the same defaults, users can't tell products apart, and "looks fine" becomes the ceiling.
- Your domain has patterns no starter kit will ship. Generic kits give you Card, Dialog, and Table. Your product lives in the states and flows specific to it. That is where consistency matters most, and exactly where a generic kit leaves you on your own.
- Products outlive frameworks. Most companies ship on more than React web: native iOS and Android, desktop, email, marketing pages. Tokens and documented decisions travel across all of them. A folder of React components does not.
- Change gets cheaper instead of more expensive. In a governed system, a rebrand or a new theme is a token change and a release. In a forked starter, it is an archaeology project across every repo that copied a Button.
- Your design system is the agent's prompt. Coding agents produce the average of what they have seen unless you give them something better. Named tokens, documented components, and written rules are the context that makes generated UI look like your product. Without them you get the training data's favorite look, which today is shadcn's defaults. This one is new, and I think it will decide whose products still look like their own in three years.
The pragmatic path
None of this means hand-rolling your own combobox. Build on commodity primitives and own the layer above them.
- Start with tokens, not components. Name color, space, type, and radius by intent, and wire Figma and code to the same source.
- Borrow behavior, own appearance. Use headless primitives such as Radix, React Aria, or Base UI for focus, keyboard, and ARIA. That work is solved. Don't redo it.
- Treat shadcn as scaffolding. It is fine for the first sprint. As you adopt each component, rename it, wrap it, and document it, so it becomes yours on purpose rather than by accident.
- Write the decisions down, including the ones you rejected and why.
- Name an owner and a contribution model before the second team copies a Button.
- Publish your rules where agents can read them: an
AGENTS.md, an MCP server, worked examples in the repo.
Start fast, then build your own
Bootstrap wasn't a mistake. Staying on it was. The teams that did well in the 2010s used it to get moving, then invested in something of their own once they understood what their product needed.
shadcn is a better Bootstrap: a better start, and more honest about what it is. Use it the same way. Start fast, then build the system only you could have built.