Avolve

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:

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:

  1. Initialize shadcn/ui: Run npx shadcn@latest init
  2. Add components gradually: Start with Button, Input, Form
  3. Replace existing components: One component at a time (non-breaking)
  4. Customize as needed: Edit source code directly in components/ui/
  5. 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:

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