Skip to main content
ASoc
Comparison

shadcn vs. Tailwind Is a Category Error (One Runs on the Other)

92 components, 13 runtime dependencies, zero UI libraries — what hand-rolling actually cost this codebase, and the seven components shadcn would have handed over.

The ASoc Team8 min read

shadcn/ui is not an alternative to Tailwind CSS — it is built on it. Tailwind is the styling layer that turns flex, p-4 and bg-primary into CSS; shadcn/ui is a set of accessible React components, styled with those same utilities, that you copy into your own repository instead of installing. Choosing "shadcn or Tailwind" is a category error. The real question is whether you hand-roll your components or start from someone else's copy.

This storefront answered it the hard way: 92 components, 13 runtime dependencies, zero UI libraries. Here is what that actually costs and buys.

The two things being compared

Tailwind CSSshadcn/ui
What it isA utility-first CSS frameworkA collection of React components
What you getClass names that compile to CSS.tsx files copied into your project
DistributionAn npm dependencyA CLI that writes source files you then own
Depends on Tailwind?Yes
Depends on React?NoYes
Gives you a dialog, combobox, date pickerNoYes
Gives you spacing, colour, layoutYesOnly by using Tailwind

Tailwind has no opinion about what a dropdown is. shadcn/ui has a very specific one, implemented over headless primitives (Radix, and more recently Base UI) that supply the keyboard handling and ARIA wiring, with Tailwind classes for the looks.

So the honest framing is: Tailwind is a given either way. The choice is between writing your own components with it, or starting from shadcn's and editing them.

What "copy, don't install" actually means

This is the genuinely interesting idea in shadcn/ui and it's an architectural choice, not a packaging detail.

A conventional component library is a dependency. You get <Dialog> from its package, you configure it through the props its authors exposed, and when your design needs something they didn't anticipate you fight the abstraction or fork it. Upgrades arrive as a version bump.

shadcn/ui inverts that. The CLI writes component source into your repo, typically under components/ui/. It's your code from that moment: edit any line, delete the parts you don't use, rename things. In exchange, upgrades are no longer a version bump — there's no package to update, and improvements upstream reach you only if you go and re-copy.

That trade is the same one this codebase made, minus the head start.

The measured cost of hand-rolling

Everything in src/components was written here. The census:

LayerComponentsClient Components
Atoms70
Molecules4119
Organisms335
Templates110
Total9224

And the dependency list that supports them — the complete runtime dependencies block, 13 entries:

@mdx-js/loader, @mdx-js/react, @next/mdx, @supabase/ssr,
@supabase/supabase-js, @vercel/analytics, lucide-react, next,
react, react-dom, rehype-slug, remark-gfm, server-only

No Radix, no Base UI, no headless-component package, no class-variance-authority, no tailwind-merge. Styling is 38 colour tokens in a 133-line globals.css, and Tailwind utilities in the markup.

The 24-to-92 ratio is the number that would change most under shadcn. Only 26% of components here need "use client", and zero atoms do — every primitive is a Server Component. Most shadcn components are interactive by nature and ship with "use client", so adopting them tends to push the client boundary outward: a Server Component that renders a shadcn <Button> is fine, but reaching for their <Tabs> or <Dialog> makes that subtree client-rendered. Where the boundary lands, and why it's a design decision rather than an accident, is the subject of atomic design in Next.js.

What hand-rolling actually costs

The cost is not the styling. It's the behaviour you don't see until someone tries to use it with a keyboard.

This repo has had to build, and then write up, each of these separately: a focus trap, a modal, a dropdown, tabs, an accordion, a tooltip, and an accessible mega menu. Every one of those is a component shadcn/ui would have handed over on day one with its ARIA roles and keyboard handling already correct.

That is the case for shadcn, and it's strong. A dialog is easy to draw and hard to get right: focus has to move in on open and back on close, Escape closes, focus is trapped while open, background content is inert, and the whole thing needs the right roles. Skipping that work is worth real money.

What this codebase got back for paying it:

  • No abstraction between design tokens and markup. The @theme block generates the utilities; components use them directly. There's no variant API to learn or extend.
  • A strict composition rule. Atoms → molecules → organisms → templates, importing downward only. shadcn's flat components/ui/ folder is deliberately unopinionated about this; it neither helps nor hinders, but it doesn't give you the structure either.
  • Server Components by default, including all 7 atoms.
  • No transitive dependency surface. 13 runtime packages is the whole supply chain.

Which one should you pick

If you…Choose
Need a dialog, combobox, command palette or date picker soonshadcn/ui — the a11y work is done and it is genuinely hard
Are building mostly marketing/content surfacesTailwind alone; most sections are a heading, a grid and a card
Have a design system that doesn't look like shadcn's defaultsTailwind alone, or expect to rewrite most of what the CLI copies
Want minimum client JavaScript in an App Router appTailwind alone; watch where "use client" lands
Have one developer and a deadlineshadcn/ui
Are shipping a template others will customizeTailwind alone — buyers inherit your dependency choices

That last row is why this repo chose as it did: every product it sells is source other people take over and modify, so each dependency is one more thing a buyer has to understand. The bundle-versus-accessibility tradeoff, measured against this same codebase, is covered in React UI libraries.

Note what's absent from that table: bundle size as a deciding factor. shadcn components are copied into your app and tree-shaken like your own code, so you ship what you use. The old "component library bloat" argument doesn't really apply — the costs that do are the client boundary and the design-divergence tax.

They are not mutually exclusive

The most common real-world setup is both: Tailwind for layout, spacing, colour and typography; shadcn/ui for the handful of components where correct behaviour is expensive. You can also copy a single shadcn component without adopting the rest — it's source code, so taking the dialog and writing your own buttons is a normal thing to do, not a hack.

If this project were starting again with the same constraints, that's likely where it would land: hand-rolled atoms and layout, and shadcn's dialog rather than a seventh hand-written focus trap.

Mistakes and troubleshooting

SymptomCauseFix
"Which should I install, shadcn or Tailwind?"Category error — shadcn requires TailwindInstall Tailwind; then decide about components
A whole page became client-rendered after adding a componentAn interactive shadcn component carries "use client", and that boundary spreads to its subtreePush it down to the smallest leaf that needs it
shadcn components don't match the rest of the siteTheir defaults are a real design system, not a neutral oneEdit the copied source — that's the point of copy-don't-install
Upstream fixed a bug but your copy still has itThere's no package to bumpRe-copy that component and re-apply your edits
Tailwind classes inside a copied component don't applyTailwind isn't scanning that path, or v4 tokens changed after the dev server startedCheck the content sources; restart the dev server after @theme edits
Hand-rolled dropdown works with a mouse, not a keyboardThe ARIA and focus behaviour was never implementedEither implement it properly or copy a primitive that has

Frequently asked questions

Is shadcn/ui a replacement for Tailwind CSS? No. It's built on Tailwind and can't work without it. It replaces the components you would otherwise write with Tailwind, not Tailwind itself.

Is shadcn/ui a component library? Not in the usual sense — you don't install it as a runtime dependency. A CLI copies component source into your project and you own it from then on, including future changes.

Do I need shadcn/ui to use Tailwind well? No. This site is 92 components and 546 prerendered pages on Tailwind with no UI library. What you give up is the accessible-behaviour head start on interactive components.

Does shadcn/ui increase bundle size compared to plain Tailwind? Only by what you use — the components are your source, bundled and tree-shaken like everything else. The real overhead is the headless primitives underneath and the client boundary interactive components introduce.

Can I use shadcn/ui components in React Server Components? You can render them from a Server Component, but interactive ones declare "use client" themselves, so that part of the tree becomes client-rendered. Presentational ones are fine either way.

Templates in this post

ASoc Remit is a digital-payments landing template built around a transactions dashboard preview, with expense-tracking and analytics blocks, a 50+ integrations marquee and a three-tier comparison pricing table. ASoc Script markets an AI copywriting product from an interactive generator hero through five writing capabilities, a 25+-language section, a filterable template gallery and three-tier pricing. ASoc Seeker is an AI keyword-research landing page with a topic-to-keywords hero, a four-benefit grid, a how-it-works walkthrough with charts, and two-tier pricing across three billing cycles.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates.

Keep reading

Comparison11 min read

Shopify vs a Next.js Storefront: What You Actually Inherit

Shopify's fee buys a checkout, tax handling and an ops backend — not hosting. The four jobs you take on by leaving, the headless hybrid, and when owning the frontend pays.

Read more
Comparison9 min read

SolidJS vs. Next.js: You're Comparing the Wrong Two Things

SolidStart is the real comparison. 24 of 92 component files here ship client JS — the number that decides fine-grained reactivity against RSC.

Read more