Shadcn
shadcn/ui Components
shadcn/ui Components Setup & Installation # Init shadcn in project npx shadcn@latest init # Add individual components (copies source to your project) npx shadcn…
shadcn/ui Components
Setup & Installation
# Init shadcn in project
npx shadcn@latest init
# Add individual components (copies source to your project)
npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add form input label
npx shadcn@latest add table data-table
npx shadcn@latest add sonner # toasts
npx shadcn@latest add command # command palette
npx shadcn@latest add sheet # side drawer
npx shadcn@latest add dropdown-menu select
npx shadcn@latest add calendar date-picker
# Components live in src/components/ui/ — you own the code, modify freelyCommon Patterns
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Dialog, DialogContent, DialogDescription,
DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from '@/components/ui/dialog';
import {
Form, FormControl, FormField, FormItem, FormLabel, FormMessage,
} from '@/components/ui/form';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { toast } from 'sonner';
// Dialog
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button variant="destructive" onClick={onConfirm}>Delete</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
// Form with react-hook-form + zod + shadcn
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { email: '', password: '' },
});
async function onSubmit(values: z.infer<typeof formSchema>) {
try {
await login(values);
toast.success('Logged in!');
} catch {
toast.error('Invalid credentials');
}
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="alice@example.com" {...field} />
</FormControl>
<FormMessage /> {/* shows validation error */}
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting} className="w-full">
{form.formState.isSubmitting ? 'Logging in...' : 'Log in'}
</Button>
</form>
</Form>
);
}cn() Utility & Theming
// cn() — merge Tailwind classes safely (avoids conflicts)
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage in custom component
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('rounded-lg border bg-card p-6 shadow-sm', className)}
{...props}
/>
);
}
// CSS variables for theming (globals.css)
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--muted: 210 40% 96.1%;
--border: 214.3 31.8% 91.4%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
}
// Button variants reference
<Button variant="default" /> // primary action
<Button variant="secondary" /> // less prominent
<Button variant="destructive" /> // dangerous action
<Button variant="outline" /> // bordered, no fill
<Button variant="ghost" /> // no border, subtle hover
<Button variant="link" /> // looks like anchor
<Button size="sm|md|lg|icon" />