---
name: shadcn-ui
description: Complete shadcn/ui component library guide including installation, configuration, and implementation of accessible React components. Use when setting up shadcn/ui, installing components, building forms with React Hook Form and Zod, customizing themes with Tailwind CSS, or implementing UI patterns like buttons, dialogs, dropdowns, tables, and complex form layouts.
language: typescript,tsx
framework: react,nextjs,tailwindcss
license: MIT
allowed-tools: Read, Write, Bash, Edit, Glob
---
# shadcn/ui Component Patterns
Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS.
## Table of Contents
- [When to Use](#when-to-use)
- [Quick Start](#quick-start)
- [Installation & Setup](#installation--setup)
- [Project Configuration](#project-configuration)
- [Core Components](#core-components)
- [Button](#button-component)
- [Input & Form Fields](#input--form-fields)
- [Forms with Validation](#forms-with-validation)
- [Card](#card-component)
- [Dialog (Modal)](#dialog-modal-component)
- [Select (Dropdown)](#select-dropdown-component)
- [Sheet (Slide-over)](#sheet-slide-over-component)
- [Menubar & Navigation](#menubar--navigation)
- [Table](#table-component)
- [Toast Notifications](#toast-notifications)
- [Advanced Patterns](#advanced-patterns)
- [Customization](#customization)
- [Next.js Integration](#nextjs-integration)
- [Best Practices](#best-practices)
- [Common Component Combinations](#common-component-combinations)
## When to Use
- Setting up a new project with shadcn/ui
- Installing or configuring individual components
- Building forms with React Hook Form and Zod validation
- Creating accessible UI components (buttons, dialogs, dropdowns, sheets)
- Customizing component styling with Tailwind CSS
- Implementing design systems with shadcn/ui
- Building Next.js applications with TypeScript
- Creating complex layouts and data displays
## Quick Start
For new projects, use the automated setup:
```bash
# Create Next.js project with shadcn/ui
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn@latest init
# Install essential components
npx shadcn@latest add button input form card dialog select
```
For existing projects:
```bash
# Install dependencies
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
# Initialize shadcn/ui
npx shadcn@latest init
```
## What is shadcn/ui?
shadcn/ui is **not** a traditional component library or npm package. Instead:
- It's a **collection of reusable components** that you can copy into your project
- Components are **yours to customize** - you own the code
- Built with **Radix UI** primitives for accessibility
- Styled with **Tailwind CSS** utilities
- Includes CLI tool for easy component installation
## Installation & Setup
### Initial Setup
```bash
# Initialize shadcn/ui in your project
npx shadcn@latest init
```
During setup, you'll configure:
- TypeScript or JavaScript
- Style (Default, New York, etc.)
- Base color theme
- CSS variables or Tailwind CSS classes
- Component installation path
### Installing Individual Components
```bash
# Install a single component
npx shadcn@latest add button
# Install multiple components
npx shadcn@latest add button input form
# Install all components
npx shadcn@latest add --all
```
### Manual Installation
If you prefer manual setup:
```bash
# Install dependencies for a specific component
npm install @radix-ui/react-slot
# Copy component code from ui.shadcn.com
# Place in src/components/ui/
```
## Project Configuration
### Required Dependencies
```json
{
"dependencies": {
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.294.0",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
}
}
```
### TSConfig Configuration
```json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
### Tailwind Configuration
```js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}
```
### CSS Variables (globals.css)
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
```
## Core Components
### Button Component
Installation:
```bash
npx shadcn@latest add button
```
Basic usage:
```tsx
import { Button } from "@/components/ui/button";
export function ButtonDemo() {
return ;
}
```
Button variants:
```tsx
import { Button } from "@/components/ui/button";
export function ButtonVariants() {
return (
);
}
```
Button sizes:
```tsx
```
With loading state:
```tsx
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
export function ButtonLoading() {
return (
);
}
```
### Input & Form Fields
#### Input Component
Installation:
```bash
npx shadcn@latest add input
```
Basic input:
```tsx
import { Input } from "@/components/ui/input";
export function InputDemo() {
return ;
}
```
Input with label:
```tsx
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function InputWithLabel() {
return (
);
}
```
Input with button:
```tsx
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function InputWithButton() {
return (
);
}
```
### Forms with Validation
Installation:
```bash
npx shadcn@latest add form
```
This installs React Hook Form, Zod, and form components.
Complete form example:
```tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { toast } from "@/components/ui/use-toast"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
})
export function ProfileForm() {
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer) {
toast({
title: "You submitted the following values:",
description: (
)
}
```
Card with form:
```tsx
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function CardWithForm() {
return (
Create projectDeploy your new project in one-click.
)
}
```
### Dialog (Modal) Component
Installation:
```bash
npx shadcn@latest add dialog
```
Basic dialog:
```tsx
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export function DialogDemo() {
return (
)
}
```
### Sheet (Slide-over) Component
Installation:
```bash
npx shadcn@latest add sheet
```
Basic sheet:
```tsx
import { Button } from "@/components/ui/button"
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
export function SheetDemo() {
return (
Edit profile
Make changes to your profile here. Click save when you're done.
)
}
```
Sheet with side placement:
```tsx
Settings
Configure your application settings here.
{/* Settings content */}
```
### Menubar & Navigation
#### Menubar Component
Installation:
```bash
npx shadcn@latest add menubar
```
Basic menubar:
```tsx
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
export function MenubarDemo() {
return (
File
New Tab ⌘T
New Window ⌘NSharePrintEdit
Undo ⌘Z
Redo ⌘YFindSearch the webFind...Find NextFind Previous
)
}
```
### Select (Dropdown) Component
Installation:
```bash
npx shadcn@latest add select
```
Basic select:
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function SelectDemo() {
return (
)
}
```
Select in form:
```tsx
(
Role
)}
/>
```
### Toast Notifications
Installation:
```bash
npx shadcn@latest add toast
```
Setup toast provider in root layout:
```tsx
import { Toaster } from "@/components/ui/toaster"
export default function RootLayout({ children }) {
return (
{children}
)
}
```
Using toast:
```tsx
import { useToast } from "@/components/ui/use-toast"
import { Button } from "@/components/ui/button"
export function ToastDemo() {
const { toast } = useToast()
return (
)
}
```
Toast variants:
```tsx
// Success
toast({
title: "Success",
description: "Your changes have been saved.",
})
// Error
toast({
variant: "destructive",
title: "Error",
description: "Something went wrong.",
})
// With action
toast({
title: "Uh oh! Something went wrong.",
description: "There was a problem with your request.",
action: Try again,
})
```
### Table Component
Installation:
```bash
npx shadcn@latest add table
```
Basic table:
```tsx
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const invoices = [
{ invoice: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },
{ invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
]
export function TableDemo() {
return (
A list of your recent invoices.InvoiceStatusMethodAmount
{invoices.map((invoice) => (
{invoice.invoice}{invoice.status}{invoice.method}{invoice.amount}
))}
)
}
```
## Customization
### Theming with CSS Variables
shadcn/ui uses CSS variables for theming. Configure in `globals.css`:
```css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
/* ... other dark mode variables */
}
}
```
### Customizing Components
Since you own the code, customize directly:
```tsx
// components/ui/button.tsx
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",
{
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",
// Add custom variant
custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
// Add custom size
xl: "h-14 rounded-md px-10 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes,
VariantProps {
asChild?: boolean
}
const Button = React.forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
```
## Next.js Integration
### App Router Setup
For Next.js 13+ with App Router, ensure components use `"use client"` directive:
```tsx
// src/components/ui/button.tsx
"use client"
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"
// ... rest of component
```
### Layout Integration
Add the Toaster to your root layout:
```tsx
// app/layout.tsx
import { Toaster } from "@/components/ui/toaster"
import "./globals.css"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
```
### Server Components
When using shadcn/ui components in Server Components, wrap them in a Client Component:
```tsx
// app/dashboard/page.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ButtonClient } from "@/components/ui/button-client"
export default function DashboardPage() {
return (
DashboardInteractive Button
)
}
```
```tsx
// src/components/ui/button-client.tsx
"use client"
import { Button } from "./button"
export function ButtonClient(props: React.ComponentProps) {
return
}
```
### Route Handlers with Forms
Create API routes for form submissions:
```tsx
// app/api/contact/route.ts
import { NextRequest, NextResponse } from "next/server"
import { z } from "zod"
const contactSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
message: z.string().min(10),
})
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const validated = contactSchema.parse(body)
// Process form data
console.log("Form submission:", validated)
return NextResponse.json({ success: true })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ errors: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
)
}
}
```
### Form with Server Action
Using Next.js 14+ Server Actions:
```tsx
// app/contact/page.tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as 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"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
const formSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
message: z.string().min(10),
})
async function onSubmit(values: z.infer) {
try {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
})
if (!response.ok) throw new Error("Failed to submit")
toast({
title: "Success!",
description: "Your message has been sent.",
})
} catch (error) {
toast({
variant: "destructive",
title: "Error",
description: "Failed to send message. Please try again.",
})
}
}
export default function ContactPage() {
const form = useForm>({
resolver: zodResolver(formSchema),
})
return (
)
}
```
### Metadata with shadcn/ui
Using shadcn/ui components in metadata:
```tsx
// app/layout.tsx
import { Metadata } from "next"
export const metadata: Metadata = {
title: {
default: "My App",
template: "%s | My App",
},
description: "Built with shadcn/ui and Next.js",
}
// app/about/page.tsx
import { Metadata } from "next"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
export const metadata: Metadata = {
title: "About Us",
description: "Learn more about our company",
}
export default function AboutPage() {
return (
About Our Company
We build amazing products with modern web technologies.
)
}
```
### Font Optimization
Optimize fonts with next/font:
```tsx
// app/layout.tsx
import { Inter } from "next/font/google"
import { Toaster } from "@/components/ui/toaster"
import { cn } from "@/lib/utils"
import "./globals.css"
const inter = Inter({ subsets: ["latin"] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
```
## Advanced Patterns
### Form with Multiple Fields
```tsx
const formSchema = z.object({
username: z.string().min(2).max(50),
email: z.string().email(),
bio: z.string().max(160).min(4),
role: z.enum(["admin", "user", "guest"]),
notifications: z.boolean().default(false),
})
export function AdvancedForm() {
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
bio: "",
role: "user",
notifications: false,
},
})
function onSubmit(values: z.infer) {
console.log(values)
}
return (
)
}
```
## Best Practices
1. **Accessibility**: Components use Radix UI primitives for ARIA compliance
2. **Customization**: Modify components directly in your codebase
3. **Type Safety**: Use TypeScript for type-safe props and state
4. **Validation**: Use Zod schemas for form validation
5. **Styling**: Leverage Tailwind utilities and CSS variables
6. **Consistency**: Use the same component patterns across your app
7. **Testing**: Components are testable with React Testing Library
8. **Performance**: Components are optimized and tree-shakeable
## Common Component Combinations
### Login Form
```tsx
LoginEnter your credentials to continue
```
## References
- Official Docs: https://ui.shadcn.com
- Radix UI: https://www.radix-ui.com
- React Hook Form: https://react-hook-form.com
- Zod: https://zod.dev
- Tailwind CSS: https://tailwindcss.com
- Examples: https://ui.shadcn.com/examples