shadcn/ui 3.3
Component distribution platform with copy-paste philosophy, built on Radix UI and Tailwind CSS, serving 8,000+ companies with 95.1K GitHub stars
What It Is
shadcn/ui 3.3 is a component distribution platform that copies accessible, customizable components into your project—not an npm package. Built on Radix UI primitives and Tailwind CSS, used by OpenAI, Adobe, and 8,000+ companies.
Market Dominance
- • 95.1K GitHub stars (top 0.1% repos)
- • 8,000+ companies using in production
- • OpenAI, Adobe, Netflix among users
- • 65+ accessible components
Revolutionary Features (v3.3)
- • 182x faster dependency resolution
- • Universal registry platform (namespaced)
- • MCP server for AI assistants
- • 7 new components (Spinner, Kbd, Field, etc.)
- • Copy-paste ownership model
- • Full customization without eject
Official Documentation
For complete component reference and guides, visit:
- → ui.shadcn.com - Official documentation
- → GitHub Repository - Source code and issues
- → CLI Documentation - Installation and usage
Why It Matters
Copy-Paste Philosophy: Code Ownership Without Vendor Lock-In
Unlike traditional component libraries (Material-UI, Ant Design) distributed via npm, shadcn/ui copies source code directly into your project. This architectural decision gives you complete ownership, customization control, and eliminates dependency hell—while still providing pre-built, accessible components.
❌ Traditional Component Libraries
// Material-UI approach npm install @mui/material @emotion/react // Package.json bloat "dependencies": { "@mui/material": "^5.x", "@mui/icons-material": "^5.x", "@emotion/react": "^11.x", "@emotion/styled": "^11.x" } // Locked to library's API <Button variant="contained">Click</Button> // Can't customize without "eject"
✅ shadcn/ui Approach
// Copy component into your project npx shadcn@latest add button // Zero new dependencies (uses existing stack) // Components added to: // components/ui/button.tsx // Full customization - it's YOUR code <Button className="your-custom-class"> Click </Button> // Modify source directly, no eject needed
Benefits of Copy-Paste Model:
- • No version conflicts: Components use your existing dependencies
- • Full customization: Edit source code without limitations
- • Bundle size control: Only ship components you actually use
- • No breaking changes: Updates are opt-in, not forced
- • Tree-shakeable: Dead code elimination works perfectly
Universal Registry Platform (v3.3)
shadcn/ui 3.3 introduces a universal component registry that works across frameworks and allows community contributions through namespaced components. This transforms shadcn/ui from a Next.js-only solution into a platform for the entire JavaScript ecosystem.
Framework Support Matrix:
First-Class:
- • Next.js (app router)
- • Remix
- • Vite + React
- • Astro + React
Community:
- • Vue (via shadcn-vue)
- • Svelte (via shadcn-svelte)
- • Solid (via kobalte)
- • Angular (community ports)
Namespaced Components:
- • @shadcn/ui (official)
- • @company/ui (private)
- • @community/ui (public)
- • Full registry ecosystem
Radix UI Foundation: Production-Grade Accessibility
Every shadcn/ui component is built on Radix UI primitives, providing WCAG 2.1 Level AA accessibility by default. This includes keyboard navigation, screen reader support, and ARIA attributes—solving the hardest parts of building accessible components.
Accessibility Features (Built-In):
- • Keyboard navigation: Tab, Arrow keys, Escape work correctly
- • Screen reader support: Complete ARIA labeling and live regions
- • Focus management: Automatic focus trapping and restoration
- • WCAG 2.1 AA compliant: Color contrast, touch targets, labels
- • RTL support: Right-to-left languages work automatically
Example: Dialog Component Accessibility
// Radix UI handles all accessibility automatically: // ✅ Focus trapped inside dialog // ✅ Escape key closes dialog // ✅ Click outside closes dialog // ✅ aria-labelledby and aria-describedby set // ✅ Screen reader announces dialog open/close // ✅ Body scroll locked when open <Dialog> <DialogTrigger asChild> <Button>Open</Button> </DialogTrigger> <DialogContent> <DialogTitle>Delete account</DialogTitle> <DialogDescription> This action cannot be undone. </DialogDescription> <Button onClick={handleDelete}>Confirm</Button> </DialogContent> </Dialog>
Technical Architecture
Project Setup
Initialize shadcn/ui (Next.js 15):
# Initialize (auto-detects Next.js, Tailwind, TypeScript) npx shadcn@latest init # Configuration prompts: # ✓ Style: New York or Default # ✓ Base color: zinc, slate, stone, gray, neutral # ✓ CSS variables: Yes (recommended for theming) # Add components npx shadcn@latest add button card input form dialog # Components copied to: # components/ui/button.tsx # components/ui/card.tsx # components/ui/input.tsx # etc.
What Gets Created:
components/ ui/ button.tsx # Copied into your project card.tsx # You own the code input.tsx # Modify freely lib/ utils.ts # Helper functions (cn, etc.) app/ globals.css # CSS variables for theming
Dark Mode Integration
next-themes Setup:
// app/layout.tsx import { ThemeProvider } from "@/components/theme-provider" export default function RootLayout({ children }) { return ( <html lang="en" suppressHydrationWarning> <body> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange > {children} </ThemeProvider> </body> </html> ) } // components/theme-toggle.tsx "use client" import { Moon, Sun } from "lucide-react" import { useTheme } from "next-themes" import { Button } from "@/components/ui/button" export function ThemeToggle() { const { setTheme, theme } = useTheme() return ( <Button variant="ghost" size="icon" onClick={() => setTheme(theme === "light" ? "dark" : "light")} > <Sun className="rotate-0 scale-100 dark:-rotate-90 dark:scale-0" /> <Moon className="absolute rotate-90 scale-0 dark:rotate-0 dark:scale-100" /> </Button> ) }
Form Validation with Zod
Type-Safe Forms:
"use client" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" const formSchema = z.object({ username: z.string().min(2).max(50), email: z.string().email(), }) export function ProfileForm() { const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { username: "", email: "" }, }) function onSubmit(values: z.infer<typeof formSchema>) { console.log(values) // Type-safe! } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> <FormField control={form.control} name="username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> {/* Auto-displays errors */} </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> ) }
Real-World Implementation
Server Components + Dialogs
Server Action Integration:
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { deleteUser } from "@/app/actions" export async function UserCard({ user }) { return ( <Dialog> <DialogTrigger asChild> <Button variant="destructive">Delete User</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Are you absolutely sure?</DialogTitle> <DialogDescription> This will permanently delete {user.name}. </DialogDescription> </DialogHeader> <form action={deleteUser}> <input type="hidden" name="userId" value={user.id} /> <Button type="submit" variant="destructive"> Confirm Delete </Button> </form> </DialogContent> </Dialog> ) }
Production Issues and Fixes
Issue #1: Tailwind Variables Not Applied
Symptom: Components unstyled or wrong colors in production
Cause: globals.css not imported or Tailwind config missing paths
// ❌ Wrong - Missing component paths export default { content: ["./app/**/*.{ts,tsx}"], } // ✅ Right - Include all paths export default { content: [ "./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", ], } // app/layout.tsx - MUST import globals.css import "./globals.css"
Issue #2: Dialog Won't Close in Server Components
Symptom: Dialog stays open after form submission
Cause: Radix UI needs "use client" for state management
// ❌ Wrong - Server Component with Dialog state export default function UserForm({ user }) { const [open, setOpen] = useState(false) // Error! return <Dialog open={open}>...</Dialog> } // ✅ Right - Client Component wrapper "use client" export function UserFormDialog({ user }) { const [open, setOpen] = useState(false) return ( <Dialog open={open} onOpenChange={setOpen}> {/* Content */} </Dialog> ) }
Issue #3: Form Validation Errors Not Showing
Symptom: No error messages despite invalid data
Cause: Missing FormMessage component
// ❌ Wrong - No FormMessage <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input {...field} /> </FormControl> {/* Missing FormMessage! */} </FormItem> )} /> // ✅ Right - Always include FormMessage <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> {/* Required for errors */} </FormItem> )} />
Decision Framework
✅ Choose shadcn/ui When:
- • Code ownership required: Need full control without vendor lock-in
- • Accessibility critical: WCAG 2.1 AA compliance built-in via Radix UI
- • Tailwind CSS stack: Already using Tailwind (perfect integration)
- • Bundle size matters: Only ship components you actually use
- • Rapid prototyping: Pre-built components with full customization
- • No update pressure: Opt-in updates, no forced breaking changes
❌ Consider Alternatives When:
- • Non-Tailwind stack: Material-UI or Ant Design better for CSS-in-JS
- • Zero customization needs: Chakra UI simpler for basic use cases
- • Team unfamiliar with Radix: Learning curve for component APIs
- • Need managed updates: Prefer automatic library updates via npm
- • Non-React frameworks: Use framework-specific ports (shadcn-vue, etc.)
Migration Strategy
Incremental Adoption Path:
- Initialize shadcn/ui: Run
npx shadcn@latest init
- Add components gradually: Start with Button, Input, Form
- Replace existing components: One component at a time (non-breaking)
- Customize as needed: Edit source code directly in components/ui/
- Remove old library: When adoption reaches 80%+, uninstall old components
🤖 For AI Assistants
Core Facts:
- • Version: 3.3.1 (October 2025)
- • Philosophy: Copy-paste, not npm package
- • Foundation: Radix UI + Tailwind CSS
- • Components: 65+ accessible primitives
- • Stars: 95.1K GitHub (top 0.1%)
Code Generation:
- • Add:
npx shadcn@latest add [component]
- • Always include
FormMessage
in forms - • Use
"use client"
for Dialog/Sheet state - • Import from
@/components/ui/...
- • Tailwind CSS required for styling
Quick Reference Template:
// shadcn/ui + Next.js 15 pattern import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogTrigger, } from "@/components/ui/dialog" export function Example() { return ( <Dialog> <DialogTrigger asChild> <Button>Open</Button> </DialogTrigger> <DialogContent> {/* Content */} </DialogContent> </Dialog> ) }
Stack Relationships
Depends On:
- • React 19 - UI library foundation
- • Tailwind CSS v4 - Styling system
- • Radix UI - Accessible primitives
- • TypeScript 5.9 - Type safety
Used By:
- • Next.js 15 - Primary framework
- • Remix - React framework
- • Vite + React - Build tool setup
- • 8,000+ companies (OpenAI, Adobe, Netflix)
Part of Avolve Software Stack - Component platform for Next.js + React + Tailwind applications