Around 2013 you could guess a startup's stack from across the room: a fixed navbar, a gray hero block everyone called a jumbotron, three columns of round icons, and a blue button with a faint gradient. Bootstrap had made a big chunk of the web look like one company built it.
And it deserved to win. Responsive layout was hard back then, browsers disagreed about nearly everything, and Bootstrap papered over both well enough that a backend developer could 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. Some bought a theme from a marketplace and layered it on top. Then Bootstrap 4 swapped floats for flexbox and Less for Sass, and plenty of teams found out their "design system" was a pile of overrides against a dependency they couldn't upgrade anymore.
The same move, with better tools
On paper, shadcn/ui learned from all of this. Its CLI copies the component source into your repo instead of installing a dependency, so the code is yours to change. Underneath are accessible headless primitives, the styling is Tailwind plus CSS variables, and the defaults look good. Most of the ways Bootstrap went wrong just don't apply.
What Bootstrap never had was AI pushing it. Ask v0, Lovable or a general coding agent for a dashboard and you'll very likely get shadcn back: neutral grays, softly rounded corners, cards with a hairline border and a plain sans-serif. The bordered card grid is this decade's jumbotron, except that nobody has to pick it: it's what comes out when the prompt doesn't ask for anything else.
The fallacy is confusing the files with the decisions
The shadcn docs are upfront about this. In their words:
"This is not a component library. It is how you build your component library."shadcn/ui documentation
Many teams read right past that line. They run init, pick a base color, nudge the radius, and call the result their design system. What they actually have is a good starting point with their logo on it.
A design system is mostly the decisions behind the components, written down somewhere people can find them: why primary is this color and where you're not allowed to use it, how dense a table should be in your product specifically, what makes something an error rather than a warning, and how both differ from the blocked states only your domain has. Around those decisions sit a shared vocabulary between design and engineering, a token source that Figma and the codebase both read from, a way for people to contribute, and someone whose job includes saying no. shadcn doesn't ship any of that, and it doesn't claim to.
Copy-paste ownership also comes with a bill. Every component you add is code you maintain with no upstream to pull fixes from. When a team moved to Tailwind v4, there was no package to bump: every edited copy had to be migrated, partly with codemods and partly by hand, and somebody had to own that work. When three product teams each copy Button and tweak it locally, you end up with three Buttons and no way to say which one is real. With Bootstrap, drift came from overrides piled on a dependency. With shadcn it comes from forks that have no parent.
What's actually better this time
I want to be fair here, because the floor really is much higher. Accessible primitives, proper keyboard support, source you own and CSS variables make a shadcn start far easier to grow out of than Bootstrap ever was. I'd use it as scaffolding without hesitating. My problem is with how many teams never take the scaffolding down.
| Bootstrap era (~2013) | shadcn era (~2026) | |
|---|---|---|
| Distribution | Package or CDN dependency | Source copied into your repo |
| Customisation | 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, neutral 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 and needs people who own it. It can take months before it pays for itself. I still think it's the better bet, for five reasons.
- A theme mostly changes colors. Your brand also lives in how dense the screens are, how things move, what gets emphasis and how the copy sounds. When every product starts from the same defaults, users stop being able to tell them apart, and "looks fine" becomes as good as it gets.
- The patterns that matter most are ones no kit ships. Generic kits give you Card, Dialog and Table. Your product lives in its own states and flows, and that's where inconsistency hurts users most. It's also exactly where a generic kit has nothing to offer.
- Your product will outlive the framework. Many companies ship on more than React web: native iOS and Android, desktop apps, email, marketing pages. Tokens and written-down decisions carry over to all of those. A folder of React components doesn't.
- Changes get cheaper over time instead of harder. In a governed system, a rebrand or a new theme is mostly a token change and a release. In a forked starter it means digging through every repo that ever copied a Button.
- AI agents need something better to copy. Coding agents produce the average of what they've seen unless you give them more specific context. Named tokens, documented components and written rules are that context, and they're what makes generated UI look like your product. In practice, your design system becomes part of the context for every screen an agent builds. Without one, you get the training data's favorite look, which right now is shadcn's defaults. This reason didn't exist in the Bootstrap era, and I suspect it's the one that will decide whose products still look like their own a few years from now.
What I'd do instead
Owning the system doesn't mean hand-rolling your own combobox. Focus handling, keyboard behavior and ARIA are solved problems, and headless libraries like Radix, React Aria or Base UI handle them well. Borrow the behavior and own the layer above it.
I'd start with tokens rather than components: name color, space, type and radius by what they're for, and wire Figma and code to the same source. shadcn is fine for the first sprint. As each component gets adopted, wrap it and document it under your own name, so it becomes yours on purpose instead of by accident. Write the decisions down, including the ones you rejected and why. Pick an owner and agree how people contribute before the second team copies a Button. And put your rules somewhere agents can read them, whether that's an AGENTS.md, an MCP server or worked examples in the repo.
Using Bootstrap was never the mistake; staying on it was. The teams that came out of the 2010s in good shape used it to get moving, then built something of their own once they understood what their product needed.
shadcn is a better Bootstrap. It's a better start, and it's more honest about what it is. I'd use it the same way: start with it, learn what your product needs, and then build the parts nobody else could have built for you.