Getting started
How components work
Every component follows the same handful of conventions. Learn them once and you can read, edit or write any file in the library.
Anatomy of a file
A component is one file in src/components/ui/. It starts with an optional "use client" directive, imports its peers, and begins with a one-line doc comment. That comment becomes its description in the registry, the CLI and this site.
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
/** Small status label with semantic pastel tones and an optional live dot. */
const pillVariants = cva("inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[11px]", {
variants: {
tone: {
neutral: "bg-surface-muted text-ink-soft",
sage: "bg-pastel-sage text-pastel-sage-ink",
},
},
defaultVariants: { tone: "neutral" },
})
export interface PillProps
extends React.HTMLAttributes<HTMLSpanElement>,
VariantProps<typeof pillVariants> {
dot?: boolean
}
export function Pill({ className, tone, dot, children, ...props }: PillProps) {
return (
<span className={cn(pillVariants({ tone }), className)} {...props}>
{dot ? <span aria-hidden className="size-1.5 rounded-full bg-current" /> : null}
{children}
</span>
)
}| Convention | What it means |
|---|---|
| Named exports | Every component is a named export. There are no default exports. |
| className last | className is always merged with cn(), so your classes win over the defaults. |
| Props spread | Remaining props are spread onto the root element, so aria-*, data-* and event handlers just work. |
| Tokens only | Colors, radii, shadows and easing come from CSS variables. No hex values in components. |
| One file | A component never imports from a shared internal folder. Sibling imports (e.g. dialog → none, footer-cta → button) are listed in the registry. |
Client and server components
Components that need state, effects or browser APIs start with "use client". The rest have no directive and render on the server, so a plain Server Component can use them without an extra client boundary.
Server-safe (49)
button · kbd · pill · separator · input · alert · callout · empty · meter · notification · progress · progress-ring · skeleton · spinner · aspect-ratio · avatar · card · comparison · list · scroll-area · stat · table · breadcrumb · stepper · timeline · jelly-loader · marquee · masonry · orbit-ring · progressive-blur · scroll-stack · flip-text · gradient-text · letter-hover · quote · text-link · text-shimmer · underline-reveal · hover-border · hover-expand · hover-icon · hover-lift · hover-slide · footer-cta · footer-legal · footer-mega · footer-simple · footer-status · social-links
Client (129)
chip · copy-button · split-button · status-button · toggle · autosize-textarea · calendar · checkbox · combobox · file-drop · number-field · otp-field · password-field · radio · range-slider · rating · search-field · select · slider · switch · tag-input · banner · gauge · status-icon · area-chart · bar-chart · code-block · color-swatch · donut-chart · heatmap · resizable · sortable-list · sparkline · command · context-menu · dialog · drawer · dropdown-menu · lightbox · popover · sheet · toast · tooltip · tour · accordion · floating-nav · pagination · segmented · sidebar · table-of-contents · tabs · ai-orb · arrow-fill-button · aura-border · blur-reveal · book-flip · circle-menu · color-picker · confetti · count-up · cover-flow · dice · dither-image · dock · dot-globe · dotted-grid · dynamic-island · falling-sand · flip-card · flow-field · folder-preview · gooey-menu · hold-button · image-sphere · ink-trail · kaleidoscope · keycap · knob · lanyard · liquid-glass · liquid-metal · liquid-switch · loupe · magnet-tabs · mesh-gradient · odometer · pan-zoom · particle-text · peel-card · pendulum-wave · plasma-ball · pluck-string · pointer-morph · radar-sweep · ripple-image · rotating-carousel · scratch-reveal · signature-pad · slide-to-confirm · split-flap · split-showcase · spotlight · stacked-cards · trading-card · velocity-marquee · warp-field · waveform · wheel-picker · curve-text · decode-text · glitch-text · highlight-text · neon-text · rolling-text · scramble-text · stagger-words · text-reveal · typewriter · weight-text · hover-card · hover-glow-grid · hover-image · hover-image-list · hover-shine · hover-tilt · magnetic-button · magnetic-link · footer-giant · footer-newsletter
These lists are generated from the registry, so they are always current.
Composition
Anything with more than one visual part is a set of small components you compose, in the same spirit as HTML itself.
<Card interactive>
<CardHeader>
<CardTitle>Interactive card</CardTitle>
<CardDescription>Lifts gently on hover.</CardDescription>
</CardHeader>
<CardFooter>
<Button size="sm" variant="ghost">Open</Button>
</CardFooter>
</Card>Compound sets: Card*, Tabs/TabsList/TabsTrigger/TabsContent, Dialog/DialogTrigger/DialogContent/DialogFooter, RadioGroup/RadioItem, Table/THead/TBody/TR/TH/TD, List/ListItem, Avatar/AvatarGroup.
Variants
Visual options are declared with class-variance-authority. Each variant is a plain string of Tailwind classes, so adding one is a one-line edit.
variants: {
variant: {
primary: "bg-ink text-surface ...",
accent: "bg-accent text-accent-ink ...",
+ brand: "bg-pastel-sky text-pastel-sky-ink hover:brightness-95",
},
}Components that export their variants (like buttonVariants) can style other elements, which is how you make a link look like a button without nesting a button in an anchor:
import Link from "next/link"
import { buttonVariants } from "@/components/ui/button"
<Link href="/pricing" className={buttonVariants({ variant: "secondary", size: "lg" })}>
See pricing
</Link>Controlled and uncontrolled
Stateful components accept both forms, like native inputs. Pass value and a change handler to control them, or defaultValue to let them manage themselves.
// uncontrolled
<Switch defaultChecked />
// controlled
const [on, setOn] = React.useState(true)
<Switch checked={on} onCheckedChange={setOn} aria-label="Notifications" />| Component | Controlled props |
|---|---|
| Switch, Checkbox | checked / onCheckedChange |
| Toggle | pressed / onPressedChange |
| Slider, Select, RadioGroup, Tabs | value / onValueChange |
| Dialog, Sheet | open / onOpenChange |
| NumberField, OtpField, SearchField | value / onChange |
| Segmented, MagnetTabs | value / onChange (required) |
| Pagination | page / onPageChange (required) |
Refs and polymorphism
Button, Input and Textarea forward refs, because those are the elements you focus programmatically. Other components expose their root through the spread props. There is no asChild: use the exported variants (above) when you need a different element.
Icons
Icons are Phosphor. Use the bold weight for controls and fill for status glyphs. In files without "use client", import from the SSR entry:
import { ArrowRight } from "@phosphor-icons/react/dist/ssr"