Expert frontend design and implementation using shadcn/ui components for Next.js and React applications...
Build modern, accessible user interfaces with shadcn/ui components in Next.js/React.
npx shadcn@latest add <component-name>Use references/component-catalog.md to find the right component for your needs.
Building a form?
Need a modal?
Displaying data?
Navigation?
User feedback?
# Install shadcn in new project
npx shadcn@latest init
# Add single component
npx shadcn@latest add button
# Add multiple components
npx shadcn@latest add button input label card dialog
# Common starter set
npx shadcn@latest add button input label card dialog toast
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
<form>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />
</div>
<Button type="submit">Submit</Button>
</div>
</form>
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"
const schema = z.object({
email: z.string().email(),
})
const form = useForm({
resolver: zodResolver(schema),
})
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Add Item</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Item</DialogTitle>
</DialogHeader>
{/* Form content here */}
</DialogContent>
</Dialog>
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { MoreHorizontal } from "lucide-react"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
Use Tailwind responsive prefixes:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Grid adapts from 1 col mobile, 2 tablet, 3 desktop */}
</div>
{/* Mobile: Sheet drawer */}
<Sheet>
<SheetTrigger className="md:hidden">
<Menu />
</SheetTrigger>
<SheetContent side="left">
<nav>{/* items */}</nav>
</SheetContent>
</Sheet>
{/* Desktop: Regular nav */}
<nav className="hidden md:flex gap-4">
{/* items */}
</nav>
npm install next-themes
// app/layout.tsx
import { ThemeProvider } from "next-themes"
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import { Moon, Sun } from "lucide-react"
const { theme, setTheme } = useTheme()
<Button
variant="outline"
size="icon"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<Sun className="h-5 w-5 rotate-0 scale-100 dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 dark:rotate-0 dark:scale-100" />
</Button>
Edit app/globals.css:
:root {
--primary: 220 70% 50%; /* Custom blue */
--secondary: 280 60% 50%; /* Custom purple */
}
Always pair inputs with labels
<Label htmlFor="email">Email</Label>
<Input id="email" />
Use semantic HTML
Add ARIA labels where needed
<Button aria-label="Close menu">
<X className="h-4 w-4" />
</Button>
Keyboard navigation
Focus management
autoFocus sparinglySee complete example: assets/examples/todo-app.tsx
Key components: Card, Checkbox, Button, Input, Dialog
Components needed:
See Dashboard patterns in composition-patterns.md
Simple form: Input + Label + Button Complex form: Form + react-hook-form + zod
See Form patterns in composition-patterns.md
Basic: Table component Advanced: Data Table with tanstack/react-table
See Data Display patterns in composition-patterns.md
shadcn/ui uses Lucide React:
npm install lucide-react
import { Check, X, Loader2, Plus, Trash2 } from "lucide-react"
<Button>
<Plus className="mr-2 h-4 w-4" />
Add Item
</Button>
{/* Loading spinner */}
<Loader2 className="h-4 w-4 animate-spin" />
<Button disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Submit
</Button>
import { Skeleton } from "@/components/ui/skeleton"
{isLoading ? (
<Skeleton className="h-12 w-full" />
) : (
<div>{content}</div>
)}
import { useToast } from "@/components/ui/use-toast"
const { toast } = useToast()
toast({
title: "Success",
description: "Item created successfully",
})
toast({
variant: "destructive",
title: "Error",
description: "Something went wrong",
})
Remember to add <Toaster /> to layout.
If the project doesn't have shadcn/ui set up yet, see setup-guide.md for complete installation and configuration instructions.
cn() utility - Properly merge Tailwind classesUser request: "Build a todo app"
Analyze requirements
Select components (check component-catalog.md)
Install components
npx shadcn@latest add card checkbox button input label dialog
Compose UI (reference composition-patterns.md and todo-app.tsx example)
Add interactivity
Refine
Result: Fully functional, accessible todo app with shadcn/ui components.