Styling
How components are styled with Tailwind v4 and semantic utilities, and how to change them.
Tailwind v4 and the theme
Components are plain Tailwind v4 class strings. theme.css maps each token into the @theme block (--color-primary: var(--primary)), so utilities like bg-primary, border-input and ring-ring exist, and the radius scale (rounded-lg, rounded-md, rounded-sm) is derived from one --radius. In another repo, bunx shadcn@latest add @cf/theme writes those tokens into your own global stylesheet (the Radix imports, :root, .dark and the @theme map), replacing the palette shadcn init left there. Keep shadcn/tailwind.css imported too: it supplies the data-open, data-closed and similar state variants the components use.
Variants are written with cva, as in buttonVariants, and every component passes its classes through cn from the cn package with the caller's className last. That is what makes <Button className="w-full"> work without a new variant.
Dark mode
Dark mode is a .dark class on an ancestor, declared with @custom-variant dark (&:where(.dark, .dark *)). The tokens switch underneath, so bg-card is white in light mode and gray-2 in dark without the component knowing. Some components (textarea, switch, input-group, field, bubble) still carry dark: opacity tweaks from their shadcn origins. I haven't removed them yet; new work shouldn't add more.
data-slot
Most parts carry a data-slot. It gives parents a stable hook: Kbd restyles itself in-data-[slot=tooltip-content], and Bubble variants reach into *:data-[slot=bubble-content]. Base UI supplies state attributes (data-checked, data-highlighted, data-disabled) that are styled the same way.
Extending
Because the registry copies source into your repo, you own the file. For a one-off, pass className. For a new permanent variant, add it to the cva in your copy. Keep in mind that re-running shadcn add will offer to overwrite local edits.
Rules
The same rules ship in DESIGN.md and the MCP server, for the agents building with this.
- must
Accept a
classNameprop and merge it last withcn(...)so callers can override any class.merge-with-cn
- should
Do not add
dark:colour classes to a component; change or add a token intheme.cssso both modes switch together.Tokens already resolve per mode under
.dark, so adark:class is a second source of truth.no-dark-colour-overrides
- should
Give each exported part a
data-slotattribute (for exampledata-slot="dialog-content") and style relationships between parts against it rather than against class names.data-slot-on-parts
- should
Change a component for one use with
className; edit the copied source only when the change should apply everywhere in your project.extend-with-classname-first