> shadcn-ui
You are an expert in shadcn/ui, the collection of reusable React components built with Radix UI and Tailwind CSS. You help developers build beautiful, accessible interfaces by copying components directly into their project (not installed as a dependency) — providing full ownership and customization of every component including buttons, dialogs, forms, tables, command palettes, toasts, and 40+ primitives.
curl "https://skillshub.wtf/TerminalSkills/skills/shadcn-ui?format=md"shadcn/ui — Copy-Paste Component Library
You are an expert in shadcn/ui, the collection of reusable React components built with Radix UI and Tailwind CSS. You help developers build beautiful, accessible interfaces by copying components directly into their project (not installed as a dependency) — providing full ownership and customization of every component including buttons, dialogs, forms, tables, command palettes, toasts, and 40+ primitives.
Core Capabilities
Installation and Usage
# Initialize in your project
npx shadcn@latest init
# Add components (copies source code into your project)
npx shadcn@latest add button dialog card input form table
npx shadcn@latest add command toast dropdown-menu sheet tabs
# Components are now in your project at components/ui/
// Full ownership — edit anything
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email"),
});
function CreateUserDialog() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { name: "", email: "" },
});
return (
<Dialog>
<DialogTrigger asChild>
<Button>Add User</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create User</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField control={form.control} name="name" render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl><Input placeholder="John Doe" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<FormField control={form.control} name="email" render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input placeholder="john@example.com" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit" className="w-full">Create</Button>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
Data Table
import { DataTable } from "@/components/ui/data-table";
import { ColumnDef } from "@tanstack/react-table";
import { Badge } from "@/components/ui/badge";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { MoreHorizontal } from "lucide-react";
const columns: ColumnDef<User>[] = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "email", header: "Email" },
{
accessorKey: "role",
header: "Role",
cell: ({ row }) => <Badge variant={row.original.role === "admin" ? "default" : "secondary"}>{row.original.role}</Badge>,
},
{
id: "actions",
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild><Button variant="ghost" size="icon"><MoreHorizontal className="h-4 w-4" /></Button></DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => editUser(row.original)}>Edit</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => deleteUser(row.original.id)}>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
];
function UsersPage() {
return <DataTable columns={columns} data={users} searchKey="name" />;
}
Command Palette
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
function CommandPalette() {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen(true); }
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Actions">
<CommandItem onSelect={() => navigate("/dashboard")}>📊 Dashboard</CommandItem>
<CommandItem onSelect={() => navigate("/settings")}>⚙️ Settings</CommandItem>
<CommandItem onSelect={() => createNewProject()}>➕ New Project</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}
Installation
npx shadcn@latest init # Setup (adds tailwind config, utils, etc.)
npx shadcn@latest add [component] # Add specific components
Best Practices
- Not a dependency — Components are copied into your project; you own the code, customize freely
- Radix primitives — Built on Radix UI; fully accessible (ARIA, keyboard navigation) out of the box
- Tailwind styling — All styles via Tailwind classes; customize with your design tokens in
globals.css - Variant system — Uses
class-variance-authority(cva) for component variants; extend with your own - Form integration —
Formcomponent wraps react-hook-form + zod; type-safe validation built-in - Theme — CSS variables in
globals.css; switch light/dark by changing variables;next-themesfor toggle - Composition — Components are composable primitives; build complex UIs by combining simple parts
- Registry — Browse all components at ui.shadcn.com; preview before adding to your project
> related_skills --same-repo
> zustand
You are an expert in Zustand, the small, fast, and scalable state management library for React. You help developers manage global state without boilerplate using Zustand's hook-based stores, selectors for performance, middleware (persist, devtools, immer), computed values, and async actions — replacing Redux complexity with a simple, un-opinionated API in under 1KB.
> zod
You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.
> xero-accounting
Integrate with the Xero accounting API to sync invoices, expenses, bank transactions, and contacts — and generate financial reports like P&L and balance sheet. Use when: connecting apps to Xero, automating bookkeeping workflows, syncing accounting data, or pulling financial reports programmatically.
> windsurf-rules
Configure Windsurf AI coding assistant with .windsurfrules and workspace rules. Use when: customizing Windsurf for a project, setting AI coding standards, creating team-shared Windsurf configurations, or tuning Cascade AI behavior.