# AGENTS.md Compiled from rules for shadcn-best-practices --- --- title: Component Imports impact: HIGH impactDescription: Prevents import path errors --- # Component Import Patterns Correct import paths for shadcn/ui components. --- title: cn() Utility impact: HIGH impactDescription: Prevents className conflicts --- # The cn() Utility Using the className merging utility. --- title: Form Building impact: HIGH impactDescription: Type-safe form validation --- # Form Building with Zod & React Hook Form Building forms with proper validation. --- title: Theming System impact: MEDIUM impactDescription: Consistent styling across themes --- # Theming System CSS variables and dark mode configuration. --- title: Data Tables impact: MEDIUM impactDescription: Reusable table patterns --- # Data Tables with TanStack Table Building accessible, sortable tables. --- title: Configuration impact: LOW impactDescription: Project setup accuracy --- # Components Configuration Understanding components.json and aliases. --- --- --- title: The cn() Utility impact: HIGH impactDescription: Prevents Tailwind class conflicts tags: className, tailwind, cn, utility --- ## The cn() Utility The `cn()` utility combines clsx and tailwind-merge for handling Tailwind className conflicts. **Incorrect:** ```typescript // Wrong - template literals don't handle conflicts className={`base-class ${variant} ${className}`} // Wrong - clsx alone misses tailwind-merge import { clsx } from "clsx" className={clsx("base", variant, className)} ``` **Correct:** ```typescript // Using cn() handles conflicts properly import { cn } from "@/lib/utils" className={cn( "base-class", variant === "default" && "bg-slate-900", className )} className={cn("rounded-md", isError && "bg-red-500")} ``` Reference: [shadcn Combining Classes](https://ui.shadcn.com/docs/components#combining-classes) --- --- title: Component Import Paths impact: HIGH impactDescription: Using correct paths prevents build errors tags: imports, paths, components --- ## Component Import Paths Always use the alias path configured in components.json (default: `@/components/ui/`). **Incorrect:** ```typescript // Wrong - relative paths import { Button } from "../../components/ui/button" import { Button } from "./components/ui/button" // Wrong - bypassing shadcn import { Button } from "@radix-ui/react-slot" ``` **Correct:** ```typescript // Use the alias path import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Card, CardHeader, CardContent } from "@/components/ui/card" // Import utilities from configured path import { cn } from "@/lib/utils" ``` Reference: [shadcn Installation](https://ui.shadcn.com/docs/installation) --- --- title: components.json Configuration impact: HIGH impactDescription: Central configuration for shadcn setup tags: config, components-json, radix, base-ui, primitive --- # components.json Configuration The `components.json` file is the central configuration for shadcn/ui projects. It defines which primitive library, styling system, and options your project uses. ## Style Options The `style` property determines which primitive library and visual preset to use: | Style | Primitive Library | Description | |-------|------------------|-------------| | `default` | Radix UI | Classic shadcn/ui style | | `new-york` | Radix UI | Modern style with unified `radix-ui` package | | `radix-vega` | Radix UI | Classic look — clean, neutral, familiar | | `radix-nova` | Radix UI | Compact layouts with reduced padding/margins | | `radix-maia` | Radix UI | Soft and rounded with generous spacing | | `radix-lyra` | Radix UI | Boxy and sharp, pairs well with mono fonts | | `radix-mira` | Radix UI | Compact, made for dense interfaces | | `base-vega` | Base UI | Classic shadcn/ui look | | `base-nova` | Base UI | Compact layouts | | `base-maia` | Base UI | Soft and rounded | | `base-lyra` | Base UI | Boxy and sharp | | `base-mira` | Base UI | Dense interfaces | ## Radix UI vs Base UI Choose **Base UI** when: - Building a new project and want the modern recommended approach - Bundle size is critical (Base UI is more lightweight) - You need modern features like multi-select, combobox with built-in autocomplete - You prefer the `render` prop pattern over `asChild` - You're starting fresh and want the latest tooling Choose **Radix UI** when: - Maintaining an existing Radix-based project - Need specific Radix features or encountered Base UI bugs - Want wider community resources and examples - Migrating from an older shadcn setup | Aspect | Radix UI | Base UI | |--------|----------|---------| | Package | `@radix-ui/react-*` or `radix-ui` | `@base-ui/react` | | Maintainer | WorkOS | MUI team | | API Pattern | `asChild` prop | `render` prop | | Bundle Size | Larger | Smaller | | Component Coverage | Mature | Growing (v1.0 stable) | ## Full Configuration Example ```json { "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.js", "css": "app/globals.css", "baseColor": "slate", "cssVariables": true }, "iconLibrary": "lucide", "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" }, "menuColor": "default", "menuAccent": "subtle", "rtl": false } ``` ## New Projects For new projects, use `npx shadcn create` which provides an interactive setup: ```bash npx shadcn create ``` This lets you choose: - Framework (Next.js, Vite, TanStack Start, etc.) - Primitive library (Radix or Base UI) - Style preset (vega, nova, maia, lyra, mira) - Base color, icons, and other options ## Migration Commands ```bash # Migrate to unified radix-ui package (for new-york style) npx shadcn@latest migrate radix # Migrate existing project to RTL support npx shadcn@latest migrate rtl # Migrate to Base UI (if choosing Base UI for new project) # Use shadcn create and select Base UI - handles setup automatically npx shadcn create ``` ## Identifying Project Library To identify which primitive library a project uses: 1. **Check components.json** — Look at the `style` field - `base-*` styles use Base UI - Other styles use Radix UI 2. **Check package.json** — Look at dependencies - Base UI: `@base-ui/react` - Radix: `@radix-ui/react-*` or `radix-ui` 3. **Check component imports** — The installed components differ - Base UI components use Base UI primitives - Radix components use Radix primitives ## Key Differences in Practice The API remains the same regardless of choice: ```tsx // Works identically whether using Radix or Base UI import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog" import { Select, SelectContent, SelectItem } from "@/components/ui/select" ``` The difference is in the underlying implementation in `components/ui/`. Reference: [components.json Schema](https://ui.shadcn.com/schema.json) Reference: [shadcn Changelog](https://ui.shadcn.com/docs/changelog) --- --- title: Data Tables with TanStack Table impact: MEDIUM impactDescription: Reusable, accessible table patterns tags: tables, tanstack, sorting, pagination, filtering --- ## Data Tables with TanStack Table Use TanStack Table (v8) for table logic with shadcn Table components. **Incorrect:** ```typescript // Building table from scratch function BadTable({ data }) { return ( {data.map((item) => ( ))}
{item.name}
) } ``` **Correct:** ```typescript import { ColumnDef, useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, } from "@tanstack/react-table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" interface User { id: string name: string email: string } const columns: ColumnDef[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "email", header: "Email" }, ] export function DataTable({ data }: { data: User[] }) { const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), }) return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : header.column.columnDef.header} ))} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {cell.getContext().getValue() as string} ))} )) ) : ( No results. )}
) } ``` Reference: [TanStack Table](https://tanstack.com/table) --- --- title: Form Building with Zod & React Hook Form impact: HIGH impactDescription: Type-safe, accessible forms tags: forms, zod, react-hook-form, validation --- ## Form Building with Zod & React Hook Form Use Zod for schema validation and React Hook Form for form state management. **Incorrect:** ```typescript // Using uncontrolled components function BadForm() { return (
) } // Missing FormControl wrapper ( )} /> ``` **Correct:** ```typescript import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" const formSchema = z.object({ email: z.string().email("Invalid email address"), password: z.string().min(8, "Password must be at least 8 characters"), }) export function LoginForm() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: "", password: "" }, }) return (
( Email )} /> ) } ``` Reference: [shadcn Forms](https://ui.shadcn.com/docs/components/form) --- --- title: Theming System impact: MEDIUM impactDescription: Consistent styling across light/dark themes tags: theming, dark-mode, css-variables, next-themes --- ## Theming System Use CSS variables defined in globals.css for theming, and next-themes for dark mode. **Incorrect:** ```typescript // Hardcoded colors ) } ``` Reference: [shadcn Theming](https://ui.shadcn.com/docs/theming)