shadcn/ui Component Library for Next.js
shadcn/ui component library for Next.js. Covers installation, component usage, customization, theming with CSS variables, form patterns with react-hook-form and Zod, and accessible component composition. Always check current docs before adding components.
License: MIT Author: Bala Version: 0.1.0
What is shadcn/ui?
shadcn/ui is not a package manager dependency. Instead, it's a collection of component source code that you copy directly into your project. This gives you:
- •Full control over component styling and behavior
- •No dependency bloat
- •Easy to customize without waiting for package updates
- •Built on Radix UI primitives for accessibility
- •Styled with Tailwind CSS by default
Installation
Initialize shadcn/ui
npx shadcn@latest init
This command:
- •Creates
components/ui/directory - •Sets up Tailwind CSS if not already configured
- •Creates necessary config files
Add Components
# Add individual components npx shadcn@latest add button npx shadcn@latest add card npx shadcn@latest add dialog # Add multiple components at once npx shadcn@latest add button card dialog form input label
Components are copied to components/ui/[component-name].tsx.
Project Structure
app/ ├── layout.tsx └── page.tsx components/ ├── ui/ │ ├── button.tsx # From shadcn │ ├── card.tsx # From shadcn │ ├── dialog.tsx # From shadcn │ ├── form.tsx # From shadcn │ └── ... # Other shadcn components ├── MyComponent.tsx # Your custom components └── ...
Theming with CSS Variables
shadcn/ui uses CSS custom properties for theming. Customize in app/globals.css:
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.6%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.6%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.6%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 9.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.6%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.6%;
--foreground: 0 0% 98%;
--card: 0 0% 3.6%;
--card-foreground: 0 0% 98%;
/* ... other dark mode variables ... */
}
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
Dark Mode
Enable in tailwind.config.ts:
// tailwind.config.ts
export default {
darkMode: 'class',
// ...
}
Then toggle the dark class on <html>:
// components/ThemeToggle.tsx
'use client'
import { useEffect, useState } from 'react'
import { Moon, Sun } from 'lucide-react'
import { Button } from '@/components/ui/button'
export function ThemeToggle() {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
const isDarkMode = document.documentElement.classList.contains('dark')
setIsDark(isDarkMode)
}, [])
const toggleDarkMode = () => {
document.documentElement.classList.toggle('dark')
setIsDark(!isDark)
}
return (
<Button variant="ghost" size="icon" onClick={toggleDarkMode}>
{isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
)
}
Customization
Modify Component Styling
Components are copied into your project, so you can edit them directly:
// components/ui/button.tsx
import * as React from "react"
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 ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
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 hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
)
Extend Components
Create wrapper components that extend shadcn components:
// components/CustomButton.tsx
import { Button } from '@/components/ui/button'
export function CustomButton(props) {
return (
<Button
className="bg-gradient-to-r from-blue-500 to-purple-600"
{...props}
/>
)
}
Form Patterns
Form with Validation (react-hook-form + Zod)
npm install react-hook-form zod @hookform/resolvers npx shadcn@latest add form input
// app/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,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
const formSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
type FormValues = z.infer<typeof formSchema>
export default function LoginForm() {
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
password: '',
},
})
const onSubmit = (data: FormValues) => {
console.log('Form data:', data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="you@example.com" {...field} />
</FormControl>
<FormDescription>
We'll never share your email.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Sign In</Button>
</form>
</Form>
)
}
Common Component Patterns
Dialog/Modal
npx shadcn@latest add dialog
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
}
Sheet (Sidebar/Drawer)
npx shadcn@latest add sheet
import { Button } from '@/components/ui/button'
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet'
export function SheetDemo() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open Menu</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Menu</SheetTitle>
<SheetDescription>
Navigation items here
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
)
}
Toast Notifications
npx shadcn@latest add toast sonner
'use client'
import { useToast } from '@/components/ui/use-toast'
import { Button } from '@/components/ui/button'
export function ToastDemo() {
const { toast } = useToast()
return (
<Button
onClick={() => {
toast({
title: 'Success',
description: 'Your action was completed.',
})
}}
>
Show Toast
</Button>
)
}
Data Table
npx shadcn@latest add table npm install @tanstack/react-table
See component-patterns.md for full data table example.
Accessibility
shadcn/ui is built on Radix UI, which provides excellent accessibility:
- •Semantic HTML
- •ARIA attributes
- •Keyboard navigation
- •Focus management
- •Screen reader support
Always maintain these patterns when customizing components.
Resources
- •Official Docs: https://ui.shadcn.com (with llms.txt)
- •Component Gallery: https://ui.shadcn.com/docs/components
- •Radix UI Docs: https://www.radix-ui.com/docs
- •Community Examples: https://github.com/shadcn-ui
- •Discord Support: https://discord.gg/pqrnqn
Doc Lookup Strategy
- •Check llms.txt: https://ui.shadcn.com/llms.txt
- •Browse component docs: https://ui.shadcn.com/docs/components/[name]
- •Radix UI docs: For underlying primitives and behavior
- •Context7 fallback: resolve-library-id("shadcn") → get-library-docs
Always verify the component documentation before adding or customizing components.