shadcn-best-practices

shadcn/ui done right. Component customization, form patterns, theming, accessibility, and composition patterns.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "shadcn-best-practices" with this command: npx skills add ofershap/shadcn-best-practices/ofershap-shadcn-best-practices-shadcn-best-practices

When to use

Use this skill when working with shadcn/ui. Agents often treat it as a conventional npm package, suggest wrong imports, skip the form patterns, and ignore the copy-paste architecture. This skill anchors correct component sourcing, form patterns with React Hook Form + Zod, theming via CSS variables, and composition patterns.

Critical Rules

1. Components live in your project, not a package

Wrong:

import { Button } from "@shadcn/ui";
import { Button } from "shadcn-ui";

Correct:

import { Button } from "@/components/ui/button";

Why: shadcn/ui is a collection of copy-paste components. You copy them into your project and own the source. There is no @shadcn/ui package to import from.

2. Use cn() for conditional classes

Wrong:

<div className={`flex ${variant === "primary" ? "bg-blue-500" : "bg-gray-500"} ${className}`}>

Correct:

import { cn } from "@/lib/utils"
<div className={cn("flex", variant === "primary" && "bg-blue-500", className)}>

Why: cn() from @/lib/utils merges Tailwind classes correctly and resolves conflicts. Template literals produce invalid combinations and unpredictable specificity.

3. Forms: React Hook Form + Zod + shadcn Form

Wrong:

<form
  onSubmit={(e) => {
    e.preventDefault(); /* manual validation */
  }}
>
  <input onChange={(e) => setValue(e.target.value)} />
</form>

Correct:

import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Form, FormField, FormItem, FormControl, FormMessage } from "@/components/ui/form"

const schema = z.object({ username: z.string().min(2) })
const form = useForm({ resolver: zodResolver(schema), defaultValues: { username: "" } })

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField control={form.control} name="username" render={({ field }) => (
      <FormItem>
        <FormControl><Input {...field} /></FormControl>
        <FormMessage />
      </FormItem>
    )} />
  </form>
</Form>

Why: shadcn Form is built for React Hook Form with Zod. Manual validation and uncontrolled inputs bypass validation, a11y, and error display.

4. Build custom components with Radix primitives

Wrong:

<div onClick={() => setOpen(!open)} role="button">
  ...
</div>

Correct:

import * as Dialog from "@radix-ui/react-dialog";
<Dialog.Root>
  <Dialog.Trigger>...</Dialog.Trigger>...
</Dialog.Root>;

Why: Radix provides accessibility, keyboard handling, and focus management. Rebuilding from divs duplicates bugs and a11y gaps.

5. Theming via CSS variables

Wrong:

<Button className="bg-blue-500 text-white hover:bg-blue-600">

Correct:

/* globals.css */
:root {
  --primary: 222.2 84% 4.9%;
  --primary-foreground: 210 40% 98%;
}
<Button>

Why: shadcn components read from CSS variables. Hardcoding colors breaks theming and dark mode.

6. Accessibility

Wrong:

<div onClick={handleClick}>Click me</div>
<button>Submit</button>

Correct:

<button onClick={handleClick} aria-label="Open menu">Open</button>
<button type="submit">Submit</button>

Why: Buttons need proper roles, labels, and keyboard support. Use semantic elements and aria attributes.

7. Use cva for component variants

Wrong:

className={cn("base", size === "sm" && "text-sm", size === "lg" && "text-lg", variant === "outline" && "border", ...)}

Correct:

import { cva } from "class-variance-authority"
const variants = cva("base", { variants: { size: { sm: "text-sm", lg: "text-lg" }, variant: { outline: "border" } } })
className={variants({ size, variant })}

Why: cva keeps variant logic declarative and type-safe. Conditional chains are hard to maintain and error-prone.

8. Dialog/Sheet/Popover: controlled state and navigation

Wrong:

<Dialog open={open} onOpenChange={() => {}}>

Correct:

<Dialog open={open} onOpenChange={setOpen}>

Why: Always pass controlled state and handle close. Close on route change or escape via onOpenChange so state stays in sync.

9. Data tables with TanStack Table

Wrong:

<table>{data.map(row => <tr>...)}</table>

Correct:

import { useReactTable, getCoreRowModel } from "@tanstack/react-table";
import { Table, TableHeader, TableBody, TableRow } from "@/components/ui/table";
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });

Why: shadcn Table is designed to work with @tanstack/react-table for sorting, filtering, and virtualization.

10. Toast: use sonner integration

Wrong:

import { toast } from "custom-toast-library";
toast("Done");

Correct:

import { toast } from "sonner";
toast.success("Done");

Why: shadcn uses sonner for Toaster. Use the configured sonner instance to match app styling and placement.

11. Never import from @shadcn/ui

Wrong:

import { Component } from "@shadcn/ui";

Correct:

import { Component } from "@/components/ui/component";

Why: Components live in your repo at @/components/ui/. Add new ones with the CLI.

12. Add components with the CLI

Wrong:

npm install @shadcn/button

Correct:

npx shadcn@latest add button

Why: The CLI copies component source and configures your project. Install adds nothing useful.

Patterns

  • Keep component source in @/components/ui/ and customize it directly
  • Use FormField, FormItem, FormControl, FormMessage for every form field
  • Define theme in globals.css with --* CSS variables
  • Use Radix primitives when extending beyond existing shadcn components
  • Prefer cn() for any conditional or composed class names

Anti-Patterns

  • Importing from @shadcn/ui or any shadcn package
  • Wrapping shadcn components instead of editing source
  • Template literals for conditional Tailwind classes
  • Uncontrolled forms or manual validation instead of React Hook Form + Zod
  • Hardcoding hex/rgb colors in components
  • Building dialogs or dropdowns from divs instead of Radix
  • Custom toast implementations instead of sonner
  • Adding components via npm install instead of shadcn CLI

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

shadcn

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shadcn

Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".

Repository Source
19.8K109.7Kshadcn
General

axure-prototype-generator

Axure 原型代码生成器 - 输出 JavaScript 格式 HTML 代码,支持内联框架直接加载可交互原型

Archived SourceRecently Updated
General

nuxt-ui

No summary provided by upstream source.

Repository SourceNeeds Review
3.5K-nuxt