--- id: shep-ai/shep/shadcn-ui version: "ec1cf2fb" license: MIT install: manual updated: 2026-07-28 --- # shadcn-ui — This skill guides you through initializing shadcn/ui, installing individual components, and configuring your project with Radix UI primitives and Tailwind CSS. Learn to build forms with validation, customize themes, and implement production-ready accessible components you own and control. Publisher: shep-ai · Stars: 239 · Updated: 2026-07-28 Install (manual): `git clone https://github.com/shep-ai/shep` ## SKILL.md # shadcn/ui Component Patterns ## Overview Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS. This skill provides comprehensive patterns for implementing production-ready components with full accessibility support. ## 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 ## Instructions 1. **Initialize Project**: Run `npx shadcn@latest init` to configure shadcn/ui 2. **Install Components**: Add components with `npx shadcn@latest add ` 3. **Configure Theme**: Customize CSS variables in globals.css for theming 4. **Import Components**: Use components from `@/components/ui/` directory 5. **Customize as Needed**: Modify component code directly in your project 6. **Add Form Validation**: Integrate React Hook Form with Zod schemas 7. **Test Accessibility**: Verify ARIA attributes and keyboard navigation ## Examples ### Complete Form with Validation ```tsx 'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; 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 formSchema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Password must be at least 8 characters'), }); export function LoginForm() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: '', password: '' }, }); return (
( Email )} /> ); } ``` ## Constraints and Warnings - **Not an NPM Package**: Components are copied to your project; you own the code - **Client Components**: Most components require "use client" directive - **Radix Dependencies**: Ensure all @radix-ui packages are installed - **Tailwind Required**: Components rely on Tailwind CSS utilities - **TypeScript**: Designed for TypeScript projects; type definitions included - **Path Aliases**: Configure @ alias in tsconfig.json for imports - **Dark Mode**: Set up dark mode with CSS variables or class strategy ## 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: (
          {JSON.stringify(values, null, 2)}
        
), }); } return (
( Username This is your public display name. )} /> ( Email )} /> ); } ``` ### Card Component Installation: ```bash npx shadcn@latest add card ``` Basic card: ```tsx import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; export function CardDemo() { return ( Card Title Card Description

Card Content

Card Footer

); } ``` 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 project Deploy 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 ( Edit profile Make changes to your profile here. Click save when you're done.
); } ``` ### 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 ⌘N Share Print Edit Undo ⌘Z Redo ⌘Y Find Search the web Find... Find Next Find 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. Invoice Status Method Amount {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 (
Dashboard Interactive Button
); } ``` ```tsx // src/components/ui/button-client.tsx 'use client'; import { Button } from './button'; export function ButtonClient(props: React.ComponentProps) { return