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.

src/components/ui/pill.tsx (abridged)
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>
  )
}
ConventionWhat it means
Named exportsEvery component is a named export. There are no default exports.
className lastclassName is always merged with cn(), so your classes win over the defaults.
Props spreadRemaining props are spread onto the root element, so aria-*, data-* and event handlers just work.
Tokens onlyColors, radii, shadows and easing come from CSS variables. No hex values in components.
One fileA 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.

tsx
<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.

src/components/ui/button.tsx
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:

tsx
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.

tsx
// uncontrolled
<Switch defaultChecked />

// controlled
const [on, setOn] = React.useState(true)
<Switch checked={on} onCheckedChange={setOn} aria-label="Notifications" />
ComponentControlled props
Switch, Checkboxchecked / onCheckedChange
Togglepressed / onPressedChange
Slider, Select, RadioGroup, Tabsvalue / onValueChange
Dialog, Sheetopen / onOpenChange
NumberField, OtpField, SearchFieldvalue / onChange
Segmented, MagnetTabsvalue / onChange (required)
Paginationpage / 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:

tsx
import { ArrowRight } from "@phosphor-icons/react/dist/ssr"