The Mental Model
You are the Conductor.
A junior dev tries to sculpt every button from clay (CSS). A senior dev picks up a Lego block that already works.
Headless UI handles accessibility, keyboard nav, and focus states.
Tailwind provides the styling layer on top.
You compose pre-built pieces into custom UIs.
The Studio
It's not a library. It's code.
Unlike Bootstrap, you don't just link a file. You copy the code into your project. You own it. If you want the button to be blue, you change the code in your folder.
- BenefitFull control. No fighting the library's opinions.
- BenefitNo version lock-in. The code is yours forever.
- TradeoffYou maintain the code. No auto-updates.
... installing components/ui/button.tsx
Front of House vs. Kitchen
Keep your "Atoms" (small parts) separate from your "Pages" (views).
The Import Alias
Stop doing "dot dot slash" gymnastics. Use the @ symbol to reach the root of your project instantly.
import Button from "../../../components/ui/button"import { Button } from "@/components/ui/button"Dumb vs. Smart Components
Separate presentation from logic for maximum reusability.
Dumb (UI) components/ui/
No state, no API calls. Just props in, HTML out. Button, Input, Card.
Smart (Feature) components/dashboard/
Has state, fetches data, handles logic. UserProfile, CheckoutForm.
The Components
shadcn/ui provides dozens of pre-built components. Here are the essential ones you'll use in almost every project.
Form Inputs
The building blocks of any interactive app.
Layout
Containers and structural components.
User Feedback
Tell users what's happening.
Modals & Menus
Content that appears above the page.
Quick Install
$ npx shadcn@latest add button input card dialog toast
# Installs 5 components at once
The Customization
Variants (The Right Way)
shadcn uses class-variance-authority (cva) to define variants. Add new styles without breaking existing ones.
// components/ui/button.tsx
const buttonVariants = cva(
"inline-flex items-center...",
{
variants: {
variant: {
default: "bg-primary...",
destructive: "bg-red-500...",
outline: "border...",
// Add your own!
brand: "bg-purple-600...",
},
},
}}
)
Global Theming
Change colors site-wide by editing CSS variables in globals.css. All components inherit automatically.
/* globals.css */
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--radius: 0.5rem;
}
.dark {
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
}
Tip: shadcn uses HSL values without the hsl() wrapper for Tailwind compatibility.
Common Patterns
✗ Anti-Patterns
Overriding with !important
If you need !important, you're fighting the library. Edit the source instead.
Wrapping in extra divs
Components already have proper styling. Use className prop, not wrapper divs.
Duplicating component code
Need a variant? Add it to the existing component, don't copy-paste.
Ignoring TypeScript props
Components have typed props. Use them for autocomplete and safety.
✓ Best Practices
Use the className prop
<Button className="w-full"> extends styles cleanly.
Add variants for reuse
Need a "brand" button? Add a variant, don't inline styles.
Compose, don't modify
Build complex UIs by combining simple components.
Tell AI about your components
"Use Button from @/components/ui/button" in every prompt.
The Exercises
Assemble the stage.
The Hire
Install Components
- →npx shadcn@latest init
- →npx shadcn@latest add button card input
- →Check your components/ui folder
- →Read the button.tsx code
The Jam
Use with AI
- →Open Claude or ChatGPT
- →"Build a login form using Button, Input, Card from @/components/ui"
- →Paste the code into page.tsx
- →See how clean the file looks
The Remix
Customize
- →Open components/ui/button.tsx
- →Find the "variants" object
- →Add a new variant called "brand"
- →Use it: <Button variant="brand">
📋 Quick Reference Cheatsheet
Initialize
npx shadcn@latest initAdd Component
npx shadcn@latest add [name]Import Pattern
import { X } from "@/components/ui/x"