Shadcn UI
Accessible Component Architecture
“Beautifully designed components that you copy and paste into your apps”
Created components/ui/button.tsx
Created components/ui/card.tsx
Created components/ui/dialog.tsx
Success! 3 components added to your codebase.
What Makes Shadcn UI Different?
Why it is taking over the React and Next.js ecosystem
Traditional Component Libraries (MUI/Chakra)
You install a massive npm package. Overriding styles is painful with complex CSS-in-JS wrappers. Upgrades break your custom designs.
The Shadcn UI Philosophy
It is NOT an npm dependency. The source code lives directly inside your src/components/ui/ directory. You own and customize every line.
The Three Pillars of Shadcn UI
How modern web building blocks unite
01 Radix UI Primitives
Handles accessibility, keyboard navigation (Tab/Arrow keys), ARIA labels, focus trapping, and screen reader announcements.
02 Tailwind CSS
Provides utility styling, responsive breakpoints, dark mode variants, and smooth hover/active transitions.
03 Lucide Icons
Clean, lightweight, tree-shakeable SVG icons that integrate seamlessly with typography and buttons.
Initializing Shadcn UI
Configuring components.json in your project
Which color would you like to use as base color? › Zinc
Would you like to use CSS variables for colors? › Yes
Where is your global CSS file? › src/app/globals.css
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Success! Project initialized with components.json
The cn() Utility & Class Merging
Safely combining conditional classes and overriding Tailwind styles
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}tailwind-merge resolves Tailwind class conflicts intelligently. If a component defines p-4 and the caller passes p-6, p-6 wins cleanly instead of both classes fighting in CSS!Adding Components via CLI
Grabbing exactly what you need without dependency bloat
Created src/components/ui/button.tsx
Created src/components/ui/badge.tsx
Created src/components/ui/card.tsx
Created src/components/ui/dialog.tsx
Done! You can now import them into any page.
The Button Component & Variants
Understanding class-variance-authority (cva)
import { Button } from '@/components/ui/button';
import { ArrowRight, Trash2 } from 'lucide-react';
export function ButtonShowcase() {
return (
<div className="flex flex-wrap items-center gap-3 p-4">
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="destructive" size="sm">
<Trash2 className="w-4 h-4 mr-2" /> Delete
</Button>
<Button variant="default" size="lg">
Get Started <ArrowRight className="w-4 h-4 ml-2" />
</Button>
</div>
);
}Card Component Architecture
Composing headers, content, and actions semantically
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
export function ProjectCard() {
return (
<Card className="w-full max-w-sm">
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle>Repository Sync</CardTitle>
<Badge variant="outline">Active</Badge>
</div>
<CardDescription>Last synchronized 2 minutes ago</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">Branches: main, feature-auth</p>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">View Details</Button>
<Button>Sync Now</Button>
</CardFooter>
</Card>
);
}Accessible Dialogs & Overlays
Full accessibility with focus traps and escape key handling
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
export function DeleteRepoDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete Project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your repository from the server.
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-3 mt-4">
<Button variant="outline">Cancel</Button>
<Button variant="destructive">Confirm Delete</Button>
</div>
</DialogContent>
</Dialog>
);
}Theming & CSS Variables
Effortless dark mode with design tokens
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 5%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
}bg-background and text-foreground, toggling the dark class changes the entire app's appearance seamlessly!Putting It Together: Full Widget
Composing multiple Shadcn components into a production UI
export function DashboardWidget() {
return (
<Card className="p-4 border-slate-800 bg-slate-900">
<div className="flex items-center justify-between mb-4">
<Badge>Production Ready</Badge>
<span className="text-xs text-slate-400">v2.4.0</span>
</div>
<h3 className="font-bold text-white text-lg">Deploy Pipeline</h3>
<p className="text-sm text-slate-400 mt-1">Zero-config deployment with Git push</p>
<div className="flex gap-2 mt-4">
<Button className="w-full">Deploy Now</Button>
</div>
</Card>
);
}Why This Architecture Wins
Completely accessible out of the box
Code is stored in your repo, not node_modules
Zero runtime performance overhead
Perfectly matches your custom design system
Curriculum Completed!
You have traversed the complete modern web development stack