Skip to main content
Back to blog

Why shadcn/ui changed how I build React interfaces

·4 min readDeveloper Tools

Most React component libraries work the same way. Install a package, import components, style them with props or theme overrides. It works until you need something the library did not anticipate, and then you are fighting the abstraction.

shadcn/ui takes a different approach. Instead of installing a package, you copy components directly into your project. They become your code. You own them completely.

How it works

shadcn/ui is not a dependency in your package.json. It is a CLI that generates component files in your project:

pnpm dlx shadcn@latest add button card badge

This creates files like src/components/ui/button.tsx in your codebase. The components are built on top of Radix UI (or Base UI in newer versions) for accessibility, and styled with Tailwind CSS.

The key difference: these are your files now. You can read them, modify them, delete parts you do not need, and extend them however you want. No library updates will break your customizations because there is no library to update.

Why this matters in practice

With a traditional component library like Material UI or Chakra, customization follows a hierarchy. You use the built-in props first, then theme overrides, then style overrides, and if none of that works, you write a wrapper component. Each layer adds complexity.

With shadcn/ui, customization is just editing a file. Want to change how the Button component handles loading states? Open button.tsx and change it. Want to remove variants you never use? Delete them. There is no abstraction to work around.

This site is built with shadcn/ui. The buttons, cards, badges, and separators are all shadcn components themed to match the Warm Stone palette. Changing the entire color scheme meant editing CSS variables, not fighting with theme provider props.

The component quality

The generated components are genuinely well-built. They use proper accessibility patterns from Radix/Base UI, handle keyboard navigation correctly, and follow WAI-ARIA guidelines. You get production-quality accessibility without having to implement it yourself.

The styling uses Tailwind CSS with class-variance-authority (CVA) for variant management. This means component variants are just CSS classes, which is easy to understand and extend:

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-lg text-sm font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        outline: "border border-border bg-background",
        ghost: "hover:bg-muted",
      },
      size: {
        default: "h-9 px-4",
        sm: "h-8 px-3 text-xs",
        lg: "h-10 px-6",
      },
    },
  }
);

Theming with CSS variables

shadcn/ui uses CSS variables for theming. Your entire color palette lives in your CSS file:

:root {
  --background: #1c1917;
  --foreground: #e7e5e4;
  --primary: #b45309;
  --primary-foreground: #ffffff;
  --border: #44403c;
  --muted: #292524;
  --muted-foreground: #78716c;
}

Every component references these variables through Tailwind classes like bg-primary and text-muted-foreground. Changing your entire theme is a matter of updating these values. No JavaScript theme objects, no provider components, no runtime overhead.

What is in the catalog

The component catalog covers most of what you need for a web application: buttons, inputs, selects, dialogs, dropdowns, tabs, cards, tables, toasts, and more. Each component is individually installable, so you only add what you use.

There are also more complex compositions like data tables, forms with validation, and command palettes. These are built from the smaller primitives and serve as good examples of how to compose components together.

When shadcn/ui is not the right choice

If you want a fully designed system out of the box where everything looks polished without any configuration, Material UI or Ant Design will get you there faster. shadcn/ui gives you unstyled primitives that you need to theme yourself.

If you are building something where you do not control the codebase long-term (like a freelance project you hand off), a traditional library with documented APIs might be easier for the next developer to maintain.

The bottom line

shadcn/ui trades the convenience of a packaged library for full ownership of your components. For projects where you care about design control and do not want to fight abstractions, it is the best approach I have found. The components are accessible, well-structured, and completely yours to modify.

Sources

Enjoying the blog? Subscribe via RSS to get new posts in your reader.

Subscribe via RSS