Back to Blog

shadcn UI: Complete Guide to the Most Popular React Component Collection (2026)

DesignRevision Editorial DesignRevision Editorial · SaaS, frontend & developer tooling
Updated February 15, 2025 15 min read
Human Written
Share:

shadcn UI changed how developers think about component libraries. Instead of installing a package and fighting with overrides, you copy the source code into your project and own every line. No runtime dependencies. No version conflicts. No specificity battles.

With 65,000+ GitHub stars and adoption by teams at Vercel, Supabase, and thousands of production applications, shadcn UI has become the default choice for React projects using Tailwind CSS. This shadcn ui guide covers everything from installation to production patterns.

Key Takeaways

If you remember nothing else:

  • shadcn UI is not a library you install. It is a collection of components you copy into your project and own entirely
  • Components are built on Radix UI primitives (accessible, headless) styled with Tailwind CSS utility classes
  • The CLI (npx shadcn-ui@latest) handles initialization and adding components. No npm package to manage
  • Theming uses CSS variables in your global stylesheet. Switch themes by changing variable values
  • 50+ components cover forms, data tables, navigation, dialogs, and more with full WAI-ARIA accessibility
  • Dark mode works out of the box through CSS variable toggling with next-themes or any class-based theme switcher

Table of Contents

  1. What Is shadcn UI?
  2. How It Differs from Traditional Libraries
  3. Installation and Setup
  4. Core Components
  5. Theming with CSS Variables
  6. Dark Mode
  7. Forms with React Hook Form and Zod
  8. Data Tables with TanStack Table
  9. Customizing Components
  10. The Registry and Custom Registries
  11. Production Best Practices
  12. Conclusion

What Is shadcn UI?

shadcn UI is a collection of beautifully designed, accessible React components that you add to your project through a CLI tool. Created by shadcn (Shadid Haque), it sits on top of two foundational technologies:

  • Radix UI provides the headless, accessible primitives. These handle keyboard navigation, focus management, ARIA attributes, and interaction patterns
  • Tailwind CSS provides the styling layer. Every visual aspect is controlled through utility classes and CSS variables

When you run npx shadcn-ui@latest add button, the CLI copies a button.tsx file into your components/ui directory. That file is yours. Edit it, extend it, or rewrite it entirely. There is no hidden dependency to update, no breaking changes from upstream releases, and no bundle size overhead from unused components.

This model resonated with developers so strongly that shadcn UI grew to 65,000+ GitHub stars, making it one of the most popular React UI projects. The Vercel dashboard, numerous SaaS starter kits, and thousands of production applications are built with shadcn components.

How It Differs from Traditional Libraries

The distinction matters because it changes how you build and maintain your UI.

Aspect shadcn UI Traditional Libraries (MUI, Chakra)
Distribution Source code copied via CLI npm package installed
Styling Tailwind utility classes + CSS variables CSS-in-JS or pre-built CSS
Customization Edit source files directly Theme overrides and props
Bundle Impact Only what you copy Full library weight
Updates Manual (your code) npm update (may break)
Dependencies Tailwind + Radix only Heavy runtime libraries
Lock-in None Vendor-specific APIs

Traditional libraries give you convenience at the cost of control. shadcn UI gives you control at the cost of some convenience. For most teams in 2026, the trade-off favors ownership, which is why shadcn UI adoption continues to accelerate.

Installation and Setup

Next.js (App Router)

The most common setup. If you are deciding between frameworks, our Vite vs Next.js comparison covers the trade-offs.

npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn-ui@latest init

The init command asks you to configure:

  • TypeScript (recommended: yes)
  • Style: Default or New York
  • Base color: Slate, Gray, Zinc, Neutral, or Stone
  • CSS variables for theming (recommended: yes)
  • components.json path configuration

This generates a components.json config file, updates your globals.css with CSS variables, and creates a lib/utils.ts file with the cn() helper function.

Vite

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npx shadcn-ui@latest init

Configure path aliases in tsconfig.json and vite.config.ts for clean imports. The CLI detects Vite and adjusts configuration accordingly.

Adding Your First Component

npx shadcn-ui@latest add button

This copies button.tsx into your components directory. Use it like any React component:

import { Button } from '@/components/ui/button'

export function MyPage() {
  return <Button variant="outline">Click me</Button>
}

Add multiple shadcn components at once:

npx shadcn-ui@latest add button card input dialog

Core Components

shadcn UI provides 50+ components organized by category. Here are the most commonly used:

Category Components Use Cases
Actions Button, Toggle, ToggleGroup CTAs, settings, toolbars
Layout Card, Sheet, Dialog, Drawer Content containers, modals, side panels
Navigation NavigationMenu, Tabs, Breadcrumb, Command App navigation, tab interfaces, command palettes
Data Display Table, Badge, Avatar, Skeleton Lists, status indicators, loading states
Forms Input, Select, Checkbox, Switch, Textarea, Form User input, settings, registration
Feedback Toast (Sonner), Alert, Progress, Tooltip Notifications, status messages, loading indicators
Advanced DataTable, Calendar, DatePicker, Combobox Complex data management, scheduling

Every component includes TypeScript types, accessibility attributes, and responsive behavior by default. The Next.js templates that ship with shadcn components give you a head start on common layouts.

Theming with CSS Variables

The theming system uses CSS variables defined in your globals.css. This approach is lighter than CSS-in-JS and works with server-side rendering without flash-of-unstyled-content issues.

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 221.2 83.2% 53.3%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --muted: 210 40% 96.1%;
    --accent: 210 40% 96.1%;
    --destructive: 0 84.2% 60.2%;
    --border: 214.3 31.8% 91.4%;
    --ring: 221.2 83.2% 53.3%;
    --radius: 0.5rem;
  }
}

Components reference these variables through Tailwind classes like bg-primary, text-foreground, and border-border. To create a custom brand theme, change the variable values. Every component updates instantly because they all reference the same variables.

Creating multiple themes is as simple as defining additional CSS classes:

.theme-ocean {
  --primary: 199 89% 48%;
  --primary-foreground: 0 0% 100%;
}

.theme-forest {
  --primary: 142 76% 36%;
  --primary-foreground: 0 0% 100%;
}

Apply the class to your root element and every shadcn component adopts the new palette.

Dark Mode

Dark mode is built into the CSS variable system. Define a .dark class with inverted variable values:

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 217.2 91.2% 59.8%;
  /* remaining variables */
}

Implementation with next-themes

npx shadcn-ui@latest add sonner
npm install next-themes

Create a theme provider:

// components/theme-provider.tsx
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"

export function ThemeProvider({ children, ...props }) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

Wrap your app layout:

// app/layout.tsx
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
  {children}
</ThemeProvider>

Toggle with a button:

"use client"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()
  return (
    <Button
      variant="ghost"
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
    >
      Toggle theme
    </Button>
  )
}

The system preference option (enableSystem) respects prefers-color-scheme automatically.

Forms with React Hook Form and Zod

shadcn UI includes a Form component that integrates React Hook Form with Zod validation. This is the recommended pattern for building forms in this shadcn ui tutorial.

npx shadcn-ui@latest add form input button
"use client"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
  Form, FormControl, FormField,
  FormItem, FormLabel, FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  name: z.string().min(2, "Name must be at least 2 characters"),
})

export function SignupForm() {
  const form = useForm<z.infer<typeof schema>>({
    resolver: zodResolver(schema),
    defaultValues: { email: "", name: "" },
  })

  function onSubmit(values: z.infer<typeof schema>) {
    // Handle form submission
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="Your name" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="[email protected]" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Sign up</Button>
      </form>
    </Form>
  )
}

The Form components handle validation messages, error states, and accessibility labels automatically. Zod schemas provide type-safe validation that runs both client-side and server-side.

Data Tables with TanStack Table

The DataTable component combines shadcn UI's Table with TanStack Table (formerly React Table) for sorting, filtering, pagination, and row selection.

npx shadcn-ui@latest add table
npm install @tanstack/react-table

Define columns:

// columns.tsx
import { ColumnDef } from "@tanstack/react-table"

export const columns: ColumnDef<User>[] = [
  { accessorKey: "name", header: "Name" },
  { accessorKey: "email", header: "Email" },
  { accessorKey: "role", header: "Role" },
]

Create the data table:

// data-table.tsx
"use client"
import {
  flexRender, getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table"
import {
  Table, TableBody, TableCell,
  TableHead, TableHeader, TableRow,
} from "@/components/ui/table"

export function DataTable({ columns, data }) {
  const table = useReactTable({
    data, columns, getCoreRowModel: getCoreRowModel(),
  })

  return (
    <Table>
      <TableHeader>
        {table.getHeaderGroups().map((headerGroup) => (
          <TableRow key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <TableHead key={header.id}>
                {flexRender(header.column.columnDef.header, header.getContext())}
              </TableHead>
            ))}
          </TableRow>
        ))}
      </TableHeader>
      <TableBody>
        {table.getRowModel().rows.map((row) => (
          <TableRow key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <TableCell key={cell.id}>
                {flexRender(cell.column.columnDef.cell, cell.getContext())}
              </TableCell>
            ))}
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

Add sorting, filtering, and pagination by importing additional TanStack Table features. The shadcn Table component handles the styling while TanStack handles the data logic. This separation makes it straightforward to add server-side data fetching for large datasets.

Customizing Components

Since you own the source code, customization is direct file editing.

Adding Variants

Components use class-variance-authority (cva) for variant management:

// components/ui/button.tsx
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        outline: "border border-input bg-background hover:bg-accent",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        // Add your custom variant:
        brand: "bg-blue-600 text-white hover:bg-blue-700",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        // Add your custom size:
        xl: "h-14 rounded-lg px-10 text-base",
      },
    },
  }
)

The cn() Utility

The cn() function merges Tailwind classes intelligently using clsx and tailwind-merge:

import { cn } from "@/lib/utils"

<Button className={cn("w-full", isActive && "bg-green-500")} />

This prevents class conflicts where bg-primary and bg-green-500 would both be applied. tailwind-merge resolves the conflict by keeping the last relevant class.

The Registry and Custom Registries

The shadcn UI registry at ui.shadcn.com hosts all official components. The CLI pulls from this registry when you run add commands.

Custom registries let teams host their own component collections. Point components.json to a custom URL:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "registries": {
    "my-team": {
      "url": "https://components.mycompany.com/registry"
    }
  }
}

Then add components from your private registry:

npx shadcn-ui@latest add my-team/data-card

This pattern works well for design systems where a core team maintains branded components and product teams consume them. It scales shadcn components across large organizations without losing the ownership model.

Production Best Practices

File Organization

Keep shadcn components in /components/ui/ untouched when possible. Create composed components in /components/ that combine UI primitives:

components/
  ui/          # shadcn primitives (button, card, input)
  dashboard/   # composed components (metric-card, data-grid)
  forms/       # form compositions (signup-form, settings-form)

Performance

  • Lazy load heavy components. Dialog, Sheet, and Calendar add weight. Use next/dynamic for components that render below the fold or on interaction
  • Tree-shaking is automatic. Since you only copy the components you use, unused shadcn components are never in your bundle
  • CSS variables render server-side. No flash of unstyled content compared to CSS-in-JS libraries

Keeping Components Updated

shadcn UI does not auto-update since you own the code. To pull improvements:

  1. Check the shadcn UI changelog for relevant updates
  2. Compare your component with the latest registry version
  3. Merge changes manually or re-add the component and port your customizations

Most teams find that after initial customization, they rarely need upstream updates. The Radix primitives handle accessibility updates independently through npm.

Integration with ORMs and Backends

When building full-stack applications, shadcn data tables pair naturally with server components and database queries. If you are choosing an ORM, our Prisma vs Drizzle comparison covers how each handles typed queries that feed directly into TanStack Table column definitions. For database hosting to back your shadcn-powered app, the Supabase vs Neon comparison breaks down the options.

Ship apps faster with AI

Generate production-ready Next.js apps from a prompt. Full code ownership, deploy anywhere, stunning design output.

Conclusion

shadcn UI represents a fundamental shift in how developers consume UI components. Instead of depending on a library that controls your styling, you get source code that you control.

The trade-off is clear: you sacrifice automatic updates for complete ownership. You sacrifice one-line npm installs for a CLI that copies files. You sacrifice opinionated design systems for a foundation you shape yourself.

For most React projects in 2026, that trade-off is worth it. The combination of Radix accessibility, Tailwind styling, and full code ownership gives you polished interfaces without the technical debt that traditional component libraries accumulate over time.

Start with npx shadcn-ui@latest init, add the components you need, and make them yours.


Related Resources

Frequently Asked Questions

Not in the traditional sense. shadcn UI is a collection of reusable components that you copy directly into your project via a CLI tool. Unlike libraries like MUI or Chakra that install as npm dependencies, shadcn UI gives you the actual source code. You own every file, can modify anything, and have zero runtime dependency on the project. This approach eliminates version conflicts, reduces bundle size, and gives you complete control over styling and behavior.

Yes. shadcn UI officially supports Next.js, Vite, Remix, Astro, and Laravel. The components are built on React with Radix UI primitives and Tailwind CSS, so any React-based framework that supports Tailwind works. Vue and Svelte ports also exist through community adapters. The CLI detects your framework and configures the project accordingly during initialization.

Tailwind CSS is a core dependency. The components use Tailwind utility classes for all styling, and the theming system relies on CSS variables that integrate with the Tailwind configuration. While you could theoretically strip out Tailwind classes and replace them with custom CSS, this defeats the purpose of using shadcn UI. If you prefer a different styling approach, consider Radix Themes or Headless UI as alternatives.

Edit the source files directly. When you add a component, the CLI copies its TypeScript file into your components directory. Change Tailwind classes, modify the JSX structure, add new props, or adjust animations. The class-variance-authority library handles component variants. Use the cn utility function to merge conditional classes. Since you own the code, there are no override limitations or specificity battles.

Yes. Components are built on Radix UI primitives which implement WAI-ARIA specifications. This includes proper ARIA roles, keyboard navigation, focus management, and screen reader support. Dialog, Dropdown, and AlertDialog components handle focus trapping and escape key behavior correctly. The accessibility is baked into the headless layer so it persists regardless of how you customize the visual styling.

The fundamental difference is distribution. MUI and Chakra install as npm packages with pre-compiled styles. shadcn UI copies source code into your project. MUI is better for teams that want Material Design consistency out of the box. Chakra is better for teams that prefer a prop-based styling API. shadcn UI is better for teams that want full code ownership, Tailwind-native styling, and zero runtime overhead from a component library. Most developers in 2026 choose shadcn UI for new projects because of its flexibility and smaller footprint.

RevKit

Next.js SaaS Starter Kit

Pre-built auth, billing, and dashboard. Launch your SaaS in days, not weeks.

200+ devs shipped with RevKit
Get RevKit
Next.js 15 Supabase Stripe TypeScript

Join 50k+ subscribers

Web dev, SaaS, growth & marketing. Weekly.

Thanks for subscribing! Check your email.

No spam, unsubscribe anytime.