FRONTEND2026-03-27📖 12 min read

Understanding the Real Difference Between shadcn/ui and Tailwind CSS

Understanding the Real Difference Between shadcn/ui and Tailwind CSS

shadcn/ui and Tailwind CSS are often confused, but they serve fundamentally different roles. This article breaks down the design philosophy, use cases, and how they work together — from an engineer's perspective.

髙木 晃宏

代表 / エンジニア

👨‍💼

If you've ever wondered "what's actually the difference between shadcn/ui and Tailwind CSS?", you're not alone. Both come up constantly in React-related conversations, and since they both deal with how things look, it's easy to mix them up at first. This article clarifies what each one actually does and how to think about using them.

What Is Tailwind CSS?

Tailwind CSS is a utility-first CSS framework. Its core philosophy is to compose styles by combining small, single-purpose classes like flex, pt-4, and text-center. Version 4, released in late 2024, marked a major evolution toward a CSS-first configuration approach.

At its core, Tailwind CSS is a styling tool. It does not provide UI components like buttons or cards — it gives you a vocabulary for defining how HTML elements look.

When I first tried Tailwind, I thought "won't all those class names get unreadable?" But after using it in real projects, I found that not having to jump between HTML and CSS files dramatically reduced cognitive overhead and boosted development speed. As the official Tailwind docs explain, the utility class approach pairs naturally with component-driven development and structurally prevents style duplication and CSS bloat.

Comparing Tailwind to Traditional CSS Methodologies

To understand where Tailwind fits, it helps to compare it against older CSS strategies like BEM (Block Element Modifier) and CSS Modules.

With BEM, you'd write something like this:

<button class="button button--primary button--large"> Submit </button>
.button { padding: 8px 16px; border-radius: 4px; } .button--primary { background-color: #3b82f6; color: white; } .button--large { padding: 12px 24px; font-size: 1.125rem; }

The same button in Tailwind CSS looks like this:

<button class="rounded bg-blue-500 px-6 py-3 text-lg text-white"> Submit </button>

With BEM, you need to design your class names and write rules in a separate CSS file. With Tailwind, everything is handled inline in the HTML. On a single button, this difference might feel trivial — but on a project with dozens of screens and hundreds of components, the benefits become undeniable. CSS bloat and class name collisions stop being problems you have to manage.

CSS Modules solve the scope problem, but you still have to manage separate files. Tailwind takes a more radical stance: just don't write custom CSS in the first place.

What Changed in Tailwind CSS v4

The v4 release in late 2024 brought significant changes to how Tailwind is configured. The JavaScript-based tailwind.config.js approach has given way to a CSS-first model where configuration lives directly in your CSS files.

/* CSS-first configuration in v4 */ @import "tailwindcss"; @theme { --color-brand: #3b82f6; --font-display: "Inter", sans-serif; }

In v3, theme customizations were written in a JavaScript config file. In v4, the design is centered around CSS custom properties (CSS variables), so configuration and styles now live in the same language. This simplifies build tool integration and makes configuration much easier to reason about.

v4 also introduced automatic content detection, eliminating the need to manually specify the content array. Tailwind now scans your project files automatically, which significantly reduces the amount of boilerplate configuration required.

What Is shadcn/ui — The "Not a Library" Philosophy

shadcn/ui is a collection of UI components built on top of Radix UI and Tailwind CSS. The key thing to understand is that shadcn/ui is not a traditional npm UI library that you install as a dependency.

When you run a command like npx shadcn@latest add button, the source code for that button component gets copied directly into your project. It doesn't go into node_modules — it becomes part of your own codebase. This "copy and own" approach is rooted in a philosophy that gives developers full ownership of their UI components.

With libraries like MUI or Ant Design, customization is constrained by the APIs they expose. Many developers have felt the tension between design freedom and library limitations. With shadcn/ui, since the code is right there in your project, adapting it to project-specific requirements feels natural.

On the accessibility front, shadcn/ui uses Radix UI primitives internally, which means WAI-ARIA compliance is baked in as a baseline. Not having to implement keyboard navigation and screen reader support from scratch is a significant practical advantage.

A Look at the Copied Code

Here's a simplified version of what shadcn/ui's Button component actually looks like:

import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent", ghost: "hover:bg-accent hover:text-accent-foreground", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", }, }, defaultVariants: { variant: "default", size: "default", }, } )

A few things worth noting here. First, cva (class-variance-authority) is used to declaratively manage styles per variant. Classes like bg-primary and text-primary-foreground are all Tailwind CSS utility classes — so reading shadcn/ui components requires knowing Tailwind.

The Slot component comes from Radix UI and enables the asChild pattern, which lets you swap the underlying element (say, to an <a> tag) while keeping the button's visual appearance:

{/* Standard button */} <Button variant="default">Click me</Button> {/* Button that renders as a link */} <Button asChild> <a href="/dashboard">Go to Dashboard</a> </Button>

Since all of this code lives in your project, changes like "tweak the destructive variant color" or "add a new variant" are as simple as editing the file directly. No GitHub issues to file, no theme override patterns to research.

The Role Radix UI Plays

Understanding shadcn/ui requires understanding Radix UI. Radix UI is a "headless UI library" — it provides zero visual styles, delivering only component behavior and accessibility.

Consider building a dropdown menu from scratch. You'd need to manage open/close state, keyboard navigation between items, Escape key handling, focus trapping, aria-expanded attribute management, and more. Doing all of this correctly is more work than it appears.

Radix UI abstracts away this "invisible complexity." A useful mental model: shadcn/ui takes the accessible, behavior-complete primitives that Radix UI provides and adds Tailwind CSS styling on top.

Radix UI (behavior & accessibility) ↑ used by shadcn/ui (component structure & style definitions) ↑ uses Tailwind CSS (utility-class-based styling)

This three-layer mental model is also useful for debugging. Style issue? Check your Tailwind configuration. Behavior issue? Consult the Radix UI docs.

The Relationship Between the Two — Different Layers, Not Competitors

This is the most important point. Tailwind CSS and shadcn/ui don't compete — they occupy different layers of the stack.

AspectTailwind CSSshadcn/ui
RoleStyling foundationUI component collection
ProvidesUtility classesCopyable component source code
Installationnpm package (dependency)CLI-based source code copy
Standalone useYesNo (requires Tailwind CSS, etc.)
Framework dependencyNone (works with any framework)React (Next.js recommended)

Looking inside a shadcn/ui component, you'll see it's styled entirely with Tailwind CSS utility classes. shadcn/ui is a component layer built on top of Tailwind CSS — which means asking "should I use Tailwind CSS or shadcn/ui?" is a false choice.

I made this mistake myself early on. I spent time trying to compare the two as alternatives, when the real question is: "Given that I'm using Tailwind CSS, do I also want to use shadcn/ui for my component layer?"

An Analogy: Construction Materials

Let's step away from the technical for a moment. Think of Tailwind CSS as bricks and tiles — the raw building materials. They don't form a structure on their own, but everything is built from them.

shadcn/ui is more like prefabricated parts with blueprints — pre-cut doors, window frames, and other commonly-needed components, ready to install or modify to fit your space. And those parts themselves are made from the bricks and tiles (Tailwind CSS).

You might wonder: where do MUI or Chakra UI fit in this analogy? They're closer to pre-built houses — high-quality and move-in ready, but harder to renovate if you want to knock down walls and rearrange the floor plan.

How shadcn/ui Compares to Other UI Libraries

vs. MUI (Material UI)

MUI is a component library built around Google's Material Design guidelines. You install it as an npm package and use it through its provided API.

{/* MUI button */} <Button variant="contained" color="primary" size="large"> Submit </Button>

MUI's strengths are its adherence to a well-established design system and its breadth of ready-to-use components. The downside is that heavy customization away from Material Design can get complicated fast — sx props, theme overrides, and deep style specificity all pile up.

I've personally spent a significant amount of time trying to restyle MUI's DatePicker for a past project. Digging into the internal DOM structure, finding the right class names to override, and iterating through the docs repeatedly — it was exhausting. With shadcn/ui, that kind of friction disappears because the DatePicker source is just a file in your project. The trade-off is that you now own the responsibility of keeping that code up to date.

vs. Headless UI Libraries

Libraries like Radix UI, Headless UI (by Tailwind Labs), and Ariakit provide behavior without any styling. shadcn/ui is essentially a styled starter kit built on top of these headless primitives (primarily Radix UI) — it removes the effort of wiring up your own styles from scratch.

Going directly to a headless UI library gives you maximum design freedom but requires full styling on your end. shadcn/ui sits in the middle: practical default styles with complete customizability.

Choosing the Right Approach for Your Project

Here's a practical breakdown of when to use each option.

Tailwind CSS alone makes sense when:

  • Your design is highly custom and you want to build components from scratch
  • You're using a non-React framework (Vue, Svelte, etc.)
  • You already have an established design system and are building to spec

Tailwind CSS + shadcn/ui makes sense when:

  • You're building an admin panel or dashboard with Next.js/React and need to move fast
  • You want accessible components without writing all the ARIA logic yourself
  • You need design consistency but don't want to sacrifice customizability

The core decision axis is: where does component ownership live? Do you accept the dependency management overhead of an external library and keep up with its version upgrades? Or do you take on the responsibility of owning the code yourself? shadcn/ui explicitly advocates for the latter, and whether that philosophy resonates with you is probably the biggest factor in the decision.

A Decision Flowchart

Here's a more concrete walkthrough for when starting a new project:

1. Check your framework. If you're not using React, shadcn/ui is off the table. For Vue, Svelte, or Astro (without React), use Tailwind CSS directly or look for framework-specific UI libraries.

2. Consider your design direction. If you need to conform to an established design system like Material Design, a library built around that system (like MUI) is the better fit. If you're building a custom design or prioritizing design freedom, Tailwind CSS + shadcn/ui is a strong choice.

3. Think about the nature of the project. For admin panels and dashboards that rely heavily on standard UI patterns (tables, forms, modals), shadcn/ui components can be dropped in as-is for maximum efficiency. For marketing sites and landing pages where each page has a distinct design, styling with Tailwind CSS directly often gives you more flexibility.

4. Consider your team's Tailwind experience. For teams newer to Tailwind, starting with shadcn/ui components is actually a great way to learn — reading the component code gives you real-world examples of how Tailwind classes are composed. On our own team, several engineers got up to speed with Tailwind patterns by reading shadcn/ui source code.

Practical Tips for Adopting shadcn/ui

A few things worth knowing before you dive in:

Only add what you need. shadcn/ui is not meant to be installed all at once. Run npx shadcn@latest add button dialog table for just the components you need right now, and add more as the project grows. This keeps your codebase lean.

Customize theme colors through CSS variables. shadcn/ui manages its theme via CSS variables like --primary and --background. Swapping in your brand colors means updating these variables once — every component picks up the change automatically.

@layer base { :root { --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; /* Update these values to match your brand colors */ } }

Document your modifications. When you change a copied component, leave a comment explaining what you changed and why. This makes it much easier to reconcile your version with upstream shadcn/ui changes if you ever want to pull in updates or new components.

Common Misconceptions

"With shadcn/ui, you don't need Tailwind CSS"

This is probably the most common misconception. As covered above, shadcn/ui components are styled with Tailwind CSS utility classes. Using shadcn/ui means using Tailwind CSS — the shadcn/ui setup guide requires Tailwind CSS to be installed as a prerequisite.

"Copied components won't stay up to date"

The concern that "if shadcn/ui updates, my local copy won't reflect it" is valid — but it's also intentional. Traditional UI libraries can require significant migration work on major version bumps (anyone who's migrated MUI from v4 to v5 knows this well). With shadcn/ui, as long as your copied code works, you have no obligation to track upstream changes. If you want to pull in improvements, you can re-add the component with the CLI or apply diffs manually.

"Tailwind CSS is just inline styles"

This criticism comes up occasionally. It's true that both put style information directly on elements, but the similarities end there.

Tailwind's utility classes are grounded in design tokens — fixed scales for spacing, color, typography, and more. p-4 always means 1rem of padding. text-blue-500 always refers to the same blue. This constraint is what keeps design consistent across the codebase. Inline styles have no such guardrails.

Tailwind also supports responsive breakpoints (md:flex), hover states (hover:bg-blue-600), dark mode (dark:bg-gray-800), and other pseudo-classes and media queries — none of which are achievable with inline styles.

Conclusion — Clarity Leads to Better Technical Decisions

Tailwind CSS is the foundational styling layer. shadcn/ui is a mechanism for delivering practical, ready-to-use components built on top of it. They operate at different levels of the stack, so they're not alternatives — they're designed to be used together.

To summarize where each one fits:

  • Tailwind CSS → The vocabulary and design tokens for writing styles
  • Radix UI → The headless layer handling accessibility and behavior
  • shadcn/ui → The mechanism that combines the two and delivers practical components into your codebase

With this three-layer model in mind, the question "which one should I pick?" becomes clearly misguided. The right question is: "Given that I'm using Tailwind CSS, what component strategy should I adopt?" — and shadcn/ui is one compelling answer to that question.

The frontend ecosystem moves fast and new tools appear constantly with similar-sounding names. Getting precise about what each tool actually does is what separates good technical decisions from noise. I hope this article gives you a clearer foundation for those decisions in your day-to-day work.

At aduce, we help teams navigate frontend technology decisions — from choosing the right stack to implementation. If you're wrestling with technology selection or UI architecture, feel free to reach out via aduce's contact page.