shadcn/ui is not a component library — it's a collection of reusable components you copy into your project. Its CLI, theming system, and customization API give you full control.
CLI — Add Components Instantly
# Initialize in your project
npx shadcn@latest init
# Add individual components
npx shadcn@latest add button dialog table form
# Add all components
npx shadcn@latest add --all
# Diff — see what changed upstream
npx shadcn@latest diff
Component API
Components you own and customize:
import { Button } from "@/components/ui/button"
import {
Dialog, DialogContent, DialogDescription,
DialogHeader, DialogTitle, DialogTrigger, DialogFooter
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function EditProfile() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">Name</Label>
<Input id="name" defaultValue="Pedro" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right">Email</Label>
<Input id="email" defaultValue="pedro@example.com" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Data Table with Sorting, Filtering, Pagination
import { DataTable } from "@/components/ui/data-table"
import { ColumnDef } from "@tanstack/react-table"
const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: ({ column }) => (
<Button variant="ghost" onClick={() => column.toggleSorting()}>
Name <ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
{
accessorKey: "email",
header: "Email"
},
{
accessorKey: "role",
header: "Role",
filterFn: (row, id, value) => value.includes(row.getValue(id))
},
{
id: "actions",
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(row.original.id)}>
Copy ID
</DropdownMenuItem>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-red-600">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
]
<DataTable columns={columns} data={users} />
Form with Validation (React Hook Form + Zod)
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
const schema = z.object({
username: z.string().min(2).max(30),
email: z.string().email(),
bio: z.string().max(160).optional()
})
export function ProfileForm() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { username: "", email: "", bio: "" }
})
function onSubmit(values: z.infer<typeof schema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField control={form.control} name="username" render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl><Input placeholder="johndoe" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
Theming with CSS Variables
@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%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
}
}
Key Takeaways
- Copy-paste components — you own the code
- CLI for adding and updating components
- Built on Radix UI — fully accessible
- TanStack Table integration for data tables
- React Hook Form + Zod for validated forms
- CSS variables for easy theming and dark mode
Explore shadcn/ui docs for all components.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)