--- description: Definitive best practices for shadcn/ui — organization, TypeScript, performance, and accessible components. globs: **/* alwaysApply: false --- # shadcn Best Practices Definitive guidelines for `shadcn/ui` development and integration. Application source lives under `src/`; components under `src/components/`. ## 1. Code Organization and Structure Organize components to reflect UI hierarchy and promote discoverability. **Rule:** Place domain-specific components under `src/components/` and reusable UI primitives under `src/components/ui`. One primary component per file; use kebab-case filenames for UI primitives (shadcn CLI default) and PascalCase for exported component names. ❌ BAD: ``` // src/components/Button.tsx // src/components/profile-card.tsx // src/components/user-settings/index.tsx (multiple components in one file) ``` ✅ GOOD: ``` // src/components/ui/button.tsx // src/components/forms/date-picker.tsx // src/components/layout/sidebar.tsx // src/components/forms/index.ts export * from "./date-picker"; export * from "./input"; ``` ## 2. Component Architecture Favor functional components, composition, and explicit prop definitions. **Rule:** Use functional components with `React.forwardRef` and `asChild` for seamless integration with Radix primitives. ❌ BAD: ```tsx // No ref forwarding, no asChild const Button = ({ children, onClick }) => ( ); ``` ✅ GOOD: ```tsx // src/components/ui/button.tsx import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return ( ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; ``` ## 3. TypeScript and API Design Enforce strict TypeScript with clear interfaces and robust validation. **Rule:** Use interfaces for component props. Validate form data with Zod schemas. Avoid `any` and prefer explicit types. ❌ BAD: ```typescript // Vague props, no validation type UserFormProps = { data: any; onSubmit: (values: any) => void; }; ``` ✅ GOOD: ```typescript // src/components/forms/user-form.tsx import { z } from "zod"; export interface UserFormProps { initialData?: UserFormData; onSubmit: (values: UserFormData) => void; } export const userFormSchema = z.object({ id: z.string().optional(), name: z.string().min(2, "Name must be at least 2 characters."), email: z.string().email("Invalid email address."), }); export type UserFormData = z.infer; ``` ## 4. Theming and Styling Leverage Tailwind CSS and `class-variance-authority` (CVA) for consistent, maintainable styling. **Rule:** Define component variants using CVA. Use the `cn` utility for conditional class merging. Centralize Tailwind configuration and design tokens. ❌ BAD: ```tsx // Inconsistent inline styles or direct class manipulation {showChart && ( Loading chart...}> )} ); } ``` ## 7. Accessibility Build inclusive UIs by leveraging Radix primitives and ARIA attributes. **Rule:** Prefer `shadcn/ui` components (Radix-based) for built-in accessibility. Ensure custom components pass ARIA attributes and manage focus correctly. ❌ BAD: ```tsx // Custom button without ARIA attributes or proper semantics
Click me
``` ✅ GOOD: ```tsx import { Button } from "@/components/ui/button"; ``` ## 8. Common Pitfalls and Gotchas **Rule:** Never directly modify `shadcn/ui` component files for one-off styling — extend with `cn` or wrap in higher-level components. Avoid `dangerouslySetInnerHTML` unless content is sanitized. ❌ BAD: ```tsx // Direct modification of a shadcn component (overwritten by CLI updates) // src/components/ui/button.tsx (modified for a single use case) ``` ```tsx // Security vulnerability
``` ✅ GOOD: ```tsx import { Button } from "@/components/ui/button"; ``` ```tsx import DOMPurify from "dompurify"; const sanitizedContent = DOMPurify.sanitize(userProvidedContent); return
; // Prefer rendering text directly when possible: // return

{userProvidedContent}

; ```