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

> A complete guide to shadcn UI covering installation, components, theming, forms, data tables, dark mode, and production best practices. Learn how to build polished interfaces with full code ownership in 2026.

Source: https://designrevision.com/blog/shadcn-ui-guide

---

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?](#what-is-shadcn-ui)
2. [How It Differs from Traditional Libraries](#how-it-differs-from-traditional-libraries)
3. [Installation and Setup](#installation-and-setup)
4. [Core Components](#core-components)
5. [Theming with CSS Variables](#theming-with-css-variables)
6. [Dark Mode](#dark-mode)
7. [Forms with React Hook Form and Zod](#forms-with-react-hook-form-and-zod)
8. [Data Tables with TanStack Table](#data-tables-with-tanstack-table)
9. [Customizing Components](#customizing-components)
10. [The Registry and Custom Registries](#the-registry-and-custom-registries)
11. [Production Best Practices](#production-best-practices)
12. [Conclusion](#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](/blog/best-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](/blog/vite-vs-nextjs) covers the trade-offs.

```bash
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](/tools/tailwind-colors#shadcn-base-colors)
- 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

```bash
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

```bash
npx shadcn-ui@latest add button
```

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

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

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

Add multiple shadcn components at once:

```bash
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](/blog/nextjs-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.

```css
@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:

```css
.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:

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

### Implementation with next-themes

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

Create a theme provider:

```tsx
// 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:

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

Toggle with a button:

```tsx
"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.

```bash
npx shadcn-ui@latest add form input button
```

```tsx
"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="you@example.com" {...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.

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

Define columns:

```tsx
// 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:

```tsx
// 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:

```tsx
// 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`:

```tsx
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:

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

Then add components from your private registry:

```bash
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](/blog/prisma-vs-drizzle) 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](/blog/supabase-vs-neon) breaks down the options.

{{ partial:cta/forge }}

## 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

- [Next.js Templates: 20 Best Options](/blog/nextjs-templates)
- [Best SaaS Starter Kits in 2026](/blog/best-saas-starter-kits)
- [Next.js Starter Kits Compared: Free vs Premium](/blog/nextjs-starter-kit-comparison)
- [Vite vs Next.js: Complete Comparison](/blog/vite-vs-nextjs)
- [Prisma vs Drizzle: Which ORM for Your Next.js Project?](/blog/prisma-vs-drizzle)
- [Neon vs Supabase: Which Postgres Platform?](/blog/supabase-vs-neon)
- [Best Shadcn UI Templates, Blocks & Themes](/blog/best-shadcn-templates)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "How to Set Up and Use shadcn UI in Your React Project",
  "description": "Step-by-step guide to installing shadcn UI, adding components, configuring themes, and building forms and data tables.",
  "totalTime": "PT30M",
  "step": [
    {
      "@type": "HowToStep",
      "position": 1,
      "name": "Initialize your project",
      "text": "Create a new Next.js or Vite project with TypeScript and Tailwind CSS enabled. Run npx shadcn-ui@latest init to configure shadcn UI and generate the components.json config file, globals.css variables, and utils.ts helper."
    },
    {
      "@type": "HowToStep",
      "position": 2,
      "name": "Add components",
      "text": "Use the CLI to add components: npx shadcn-ui@latest add button card input dialog. Each command copies the component source code into your components/ui directory with full TypeScript types and Tailwind styling."
    },
    {
      "@type": "HowToStep",
      "position": 3,
      "name": "Configure theming",
      "text": "Edit the CSS variables in globals.css to match your brand colors. Set primary, secondary, background, foreground, and accent values. All components reference these variables automatically through Tailwind classes."
    },
    {
      "@type": "HowToStep",
      "position": 4,
      "name": "Set up dark mode",
      "text": "Install next-themes and create a ThemeProvider component. Wrap your app layout with the provider using attribute class and enableSystem. Define dark mode CSS variables in a .dark class in globals.css."
    },
    {
      "@type": "HowToStep",
      "position": 5,
      "name": "Build forms with validation",
      "text": "Add the Form component with npx shadcn-ui@latest add form. Define Zod schemas for type-safe validation. Use React Hook Form with the zodResolver to connect validation to shadcn form fields with automatic error messaging."
    },
    {
      "@type": "HowToStep",
      "position": 6,
      "name": "Customize components",
      "text": "Edit component source files directly in your components/ui directory. Add custom variants using class-variance-authority. Use the cn utility function to merge conditional Tailwind classes without conflicts."
    }
  ]
}
</script>
