Published on

What is shadcn/ui? Understanding Its Purpose, Benefits, and How to Use It in Modern React Apps

Authors

    What is shadcn/ui? Understanding Its Purpose, Benefits, and How to Use It in Modern React Apps

    Modern React projects often face a tough balance: should you use a prebuilt UI library (fast but inflexible) or design your own system (flexible but slow to build)?
    That’s where shadcn/ui comes in, a new approach to UI development that gives you full control over your code while offering the polish of a mature component library.

    In this guide, we’ll explore what shadcn/ui is, why it’s become so popular among React and Next.js developers, and how you can use it to speed up your own UI work without losing flexibility.

    What Is shadcn/ui?

    shadcn/ui (often written simply as shadcn) is not a traditional npm package or a themeable component library like Material UI or Chakra UI.
    Instead, it’s a collection of high-quality, open-source React components built with:

    • Tailwind CSS (for styling)
    • Radix UI (for accessible, unstyled primitives)
    • Lucide Icons (for clean, consistent SVG icons)

    But here’s the twist:

    Instead of installing it as a dependency, you copy the actual source code of each component into your own project.

    That means every button, dialog, or dropdown lives in your own codebase, editable, themeable, and version-controlled by you.

    You can think of it as a component generator or UI starter kit, not a dependency.

    How It Works

    The shadcn/ui project provides a CLI tool that scaffolds components directly into your project:

    npx shadcn-ui@latest add button
    

    This command downloads a prebuilt, accessible Button component and adds it under your local components/ui/ folder. From there, you can customize it, change Tailwind classes, tweak variants, or adapt it to your design system.

    Every component is just React + Tailwind + Radix, no hidden logic or runtime dependency.

    Why Developers Use shadcn/ui

    1. Full Ownership of Code

    Unlike UI libraries where you rely on opaque dependencies, shadcn gives you the full component source. You can:

    • Edit styles directly
    • Add props or variants
    • Integrate custom logic (like analytics or permissions)

    It’s your UI, your rules, no waiting for maintainers to merge a PR.

    2. Built on Proven Foundations

    Each component is built using:

    • Tailwind CSS → utility-first styling
    • Radix UI → accessible and unstyled base primitives
    • Lucide Icons → modern SVG icons
    • clsx / tailwind-variants → clean conditional class management

    This ensures your components are both accessible and production-ready, following design system best practices out of the box.

    3. Consistency and Speed

    You get a cohesive, beautiful starting point for your UI:

    • Consistent spacing, typography, and color system
    • Dark mode and theming via CSS variables
    • Ready-to-use layout and interactive patterns (dropdowns, modals, tooltips)

    You can start shipping UIs that feel designed, without reinventing every component.

    4. No Vendor Lock-In

    Because the components are just code, you can:

    • Fork them
    • Replace them
    • Mix with other libraries

    If shadcn/ui ever changes or you outgrow it, your components still belong entirely to you.

    Common Components and Examples

    Here are some of the most popular shadcn/ui components and what they look like in action.

    Example 1: Button

    import { Button } from '@/components/ui/button'
    
    export default function Example() {
      return (
        <div className="space-x-2">
          <Button>Default</Button>
          <Button variant="outline">Outline</Button>
          <Button variant="destructive">Delete</Button>
        </div>
      )
    }
    

    Why it’s great:

    • Built-in variants like outline, destructive, and ghost.
    • Fully themeable with Tailwind’s design tokens.
    • Simple enough to customize without losing consistency.

    Understanding the Button Variants

    When you run:

    npx shadcn-ui@latest add button
    

    The CLI creates a button.tsx file inside components/ui/, typically like this:

    // components/ui/button.tsx
    import * as React from 'react'
    import { cva } 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 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
      {
        variants: {
          variant: {
            default: 'bg-primary text-primary-foreground hover:bg-primary/90',
            outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
            destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
            ghost: 'hover:bg-accent hover:text-accent-foreground',
            link: 'text-primary underline-offset-4 hover:underline',
          },
          size: {
            default: 'h-10 px-4 py-2',
            sm: 'h-9 rounded-md px-3',
            lg: 'h-11 rounded-md px-8',
            icon: 'h-10 w-10',
          },
        },
        defaultVariants: {
          variant: 'default',
          size: 'default',
        },
      }
    )
    
    export interface ButtonProps
      extends React.ButtonHTMLAttributes<HTMLButtonElement>,
        VariantProps<typeof buttonVariants> {}
    
    function Button({ className, variant, size, ...props }: ButtonProps) {
      return <button className={cn(buttonVariants({ variant, size, className }))} {...props} />
    }
    
    export { Button, buttonVariants }
    

    So What Are outline, destructive, and ghost?

    They’re style variants pre-defined in that buttonVariants configuration. You don’t need to define them manually, they come with the generated component by default.

    Each variant maps to a different Tailwind class pattern:

    VariantVisual StyleTypical Use
    defaultSolid primary button (bg-primary, white text)Main call to action (e.g., “Save”, “Submit”).
    outlineTransparent background, border visibleSecondary action or less emphasis (“Cancel”).
    destructiveRed/danger color (bg-destructive)Actions with destructive consequences (“Delete”, “Remove”).
    ghostMinimal styling, subtle hoverLow emphasis buttons (e.g., icons, secondary links).
    linkStyled like a text linkFor inline navigation inside text.

    So, when you write:

    <Button variant="destructive">Delete</Button>
    

    you’re telling the Button component to use the red danger style, because the variant maps to bg-destructive and text-destructive-foreground.

    What Are “Tailwind Design Tokens”?

    “Design tokens” are named color and spacing variables used throughout your theme. In shadcn/ui, these are defined in your globals.css (or tailwind.config.js) like so:

    :root {
      --background: 0 0% 100%;
      --foreground: 222.2 47.4% 11.2%;
      --primary: 222.2 47.4% 11.2%;
      --primary-foreground: 210 40% 98%;
      --destructive: 0 84.2% 60.2%;
      --destructive-foreground: 210 40% 98%;
      /* etc. */
    }
    

    Then Tailwind references them as “tokens” using its theme() syntax or class shortcuts:

    className = 'bg-primary text-primary-foreground hover:bg-primary/90'
    

    These tokens:

    • Keep colors consistent across components.
    • Support dark mode automatically via CSS variables.
    • Let designers and developers share one visual language (e.g., “primary”, “accent”, “destructive”) instead of raw hex codes.

    Why This Is Useful

    • You can add or modify variants (e.g., success, warning) easily in the same button.tsx.
    • Your UI stays visually consistent since all variants pull from shared design tokens.
    • Updating your theme (colors, spacing, etc.) instantly updates all components.
    • It’s flexible, use the built-in variants or extend them to match your brand.

    In short: Those variants (outline, destructive, ghost, etc.) are prebuilt, design-token-powered styles you can extend or override. They’re part of what makes shadcn/ui components production-ready out of the box, consistent, theme-aware, and easily customizable.

    Example 2: Dialog (Modal)

    import {
      Dialog,
      DialogContent,
      DialogHeader,
      DialogTitle,
      DialogTrigger,
    } from '@/components/ui/dialog'
    
    export default function Example() {
      return (
        <Dialog>
          <DialogTrigger asChild>
            <Button>Open Dialog</Button>
          </DialogTrigger>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Confirm Action</DialogTitle>
            </DialogHeader>
            <p>This action cannot be undone.</p>
          </DialogContent>
        </Dialog>
      )
    }
    

    Why it’s great: The dialog uses Radix primitives under the hood, meaning it’s keyboard-accessible, screen-reader-friendly, and responsive out of the box.

    Example 3: Input and Form Components

    import { Input } from '@/components/ui/input'
    import { Label } from '@/components/ui/label'
    
    export default function LoginForm() {
      return (
        <form className="space-y-4">
          <div>
            <Label htmlFor="email">Email</Label>
            <Input id="email" type="email" placeholder="you@example.com" />
          </div>
          <Button type="submit">Login</Button>
        </form>
      )
    }
    

    Clean, minimal, and easy to extend for any authentication or settings page.

    When (and When Not) to Use shadcn/ui

    ✅ Use It When

    • You’re building a custom product (SaaS, dashboard, app) and want consistent UI fast.
    • You need full control over design and accessibility.
    • You’re already using Tailwind CSS and Next.js.

    ❌ Avoid It When

    • You prefer a drop-in design system (like MUI, Chakra UI).
    • You don’t use Tailwind (it’s tightly coupled).
    • You want to move extremely fast without touching component code.

    Getting Started (Quick Setup)

    1. Install Tailwind CSS (if not already):

      npx create-next-app my-app
      cd my-app
      npx tailwindcss init -p
      
    2. Initialize shadcn/ui

      npx shadcn-ui@latest init
      
    3. Add components

      npx shadcn-ui@latest add button card dialog input
      
    4. Import and customize Your new components will appear in components/ui/.

    🧩 shadcn vs Other UI Libraries

    Featureshadcn/uiChakra UIMaterial UIHeadless UI
    Code ownership✅ You own it❌ Library controlled❌ Library controlled✅ You own it
    StylingTailwindStyled SystemEmotion / CSSTailwind
    Accessibility✅ Radix UI
    Theming✅ (CSS variables + Tailwind)⚙️ Custom
    Learning curve🟢 Low🟢 Medium🔴 High🟡 Medium
    Customization freedom🟢 Full🟡 Partial🔴 Limited🟢 Full

    Key Takeaways

    • shadcn/ui isn’t a package, it’s a copy-and-own component model.
    • You get real control, no lock-in, and great developer experience.
    • Built with Tailwind, Radix, and modern React patterns, it’s ideal for teams who want a clean, accessible, and extendable design foundation.

    It’s the perfect middle ground between “build everything yourself” and “depend on a heavy UI library.”