shadcn/ui conventions
You own the components
- shadcn/ui is open code copied into your project, not a runtime dependency. The components live in
components/uiand you edit them. - Customize in place. Don't build a wrapper layer around them or try to
npm installthe components. - Add with the CLI:
npx shadcn@latest add button dialog …(runnpx shadcn@latest initonce to scaffoldcomponents.json+@/lib/utils).
Styling
- Merge classes with
cn()from@/lib/utils(clsx + tailwind-merge). Never concatenate class strings by hand —cn()resolves Tailwind conflicts. - Define variants with
cva(class-variance-authority) and surface them via props (e.g.variant,size). One component, many variants — not many components. - Forward
classNameand spread...propsso callers can extend a component without forking it.
Radix + accessibility
- Components are built on Radix primitives — keep their structure (Trigger / Content / Portal) and
data-[state=…]styling hooks intact. - Interactive components are client components: keep the
"use client"directive. Uselucide-reactfor icons.
Theming
- Use semantic tokens (
bg-background,text-foreground,text-muted-foreground,border-input,ring), driven by CSS variables — not raw color utilities likebg-zinc-900. - Keep light/dark in the CSS variables; don't branch colors in component logic.
Anti-patterns
- ❌ Re-publishing
components/uias an internal package instead of editing in place. - ❌
`px-2 ${cond ? "bg-red-500" : ""}`instead ofcn("px-2", cond && "bg-red-500"). - ❌ Hardcoded hex/
bg-*-500colors that bypass the theme tokens. - ❌ Dropping Radix sub-parts or
"use client"and reimplementing primitives by hand.