DEV Community

Alex Spinov
Alex Spinov

Posted on

shadcn/ui Has a Free API: Copy-Paste React Components That You Actually Own

What is shadcn/ui?

shadcn/ui is not a component library — it's a collection of reusable React components that you copy into your project. Unlike Material UI or Chakra where you depend on a package, shadcn/ui components live in YOUR codebase. You own them, customize them, and they never break due to library updates.

Why shadcn/ui?

  • You own the code — components copied into your project, not imported from node_modules
  • Built on Radix UI — accessible, unstyled primitives under the hood
  • Tailwind CSS — style with utility classes, fully customizable
  • TypeScript — full type safety out of the box
  • CLI installer — add components with one command
  • Beautiful defaults — looks professional without any customization

Quick Start

# Initialize in your Next.js/Vite project
npx shadcn@latest init

# Add components
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog
npx shadcn@latest add form
npx shadcn@latest add table
Enter fullscreen mode Exit fullscreen mode

Usage

import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';

export function Dashboard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Welcome Back</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <Input placeholder="Search..." />
        <div className="flex gap-2">
          <Button>Primary Action</Button>
          <Button variant="outline">Secondary</Button>
          <Button variant="destructive">Delete</Button>
          <Button variant="ghost">Ghost</Button>
        </div>
      </CardContent>
    </Card>
  );
}
Enter fullscreen mode Exit fullscreen mode

Forms with React Hook Form + Zod

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Form, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

export function LoginForm() {
  const form = useForm({ resolver: zodResolver(schema) });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField control={form.control} name="email" render={({ field }) => (
          <FormItem>
            <FormLabel>Email</FormLabel>
            <Input {...field} />
            <FormMessage />
          </FormItem>
        )} />
        <FormField control={form.control} name="password" render={({ field }) => (
          <FormItem>
            <FormLabel>Password</FormLabel>
            <Input type="password" {...field} />
            <FormMessage />
          </FormItem>
        )} />
        <Button type="submit">Login</Button>
      </form>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Data Tables

import { DataTable } from '@/components/ui/data-table';
import { ColumnDef } from '@tanstack/react-table';

const columns: ColumnDef<User>[] = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'email', header: 'Email' },
  { accessorKey: 'role', header: 'Role',
    cell: ({ row }) => (
      <Badge variant={row.getValue('role') === 'admin' ? 'default' : 'secondary'}>
        {row.getValue('role')}
      </Badge>
    )
  },
];

export function UsersTable({ data }: { data: User[] }) {
  return <DataTable columns={columns} data={data} />;
}
Enter fullscreen mode Exit fullscreen mode

Theming

/* globals.css — customize your design system */
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --destructive: 0 84.2% 60.2%;
    --radius: 0.5rem;
  }
  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
  }
}
Enter fullscreen mode Exit fullscreen mode

shadcn/ui vs Alternatives

Feature shadcn/ui Material UI Chakra UI Ant Design
Code ownership You own it Package dep Package dep Package dep
Customization Full control Theme + sx Theme + props Less config
Bundle size Only what you use 300KB+ 200KB+ 400KB+
Accessibility Radix primitives Built-in Built-in Partial
Styling Tailwind Emotion/styled Emotion Less/CSS
Breaking updates Never Major versions Major versions Frequent

Real-World Impact

A team used Material UI v4. When MUI v5 launched with breaking changes, the migration took 3 weeks — every styled component had to be rewritten. With shadcn/ui: the components are in YOUR codebase. There are no library updates to break things. When the team wanted to change the button style, they edited one file — not fought with theme overrides.


Building modern React UIs? I help teams set up design systems that scale. Contact spinov001@gmail.com or explore my data tools on Apify.

Top comments (0)