Components
Read when: choosing what to build a piece of UI out of, adding a shadcn-vue component, or
writing layout classes.
Owns: the registry primitive for each need, the composition rules the registry expects, layout
class rules, and how to keep the vendored components/ui/ healthy.
TL;DR
- Check the registry before you style a
div. A hand-built version misses the focus ring, dark mode, RTL and keyboard behaviour. components/ui/is written by the shadcn-vue CLI only. Customise by wrapping incomponents/, passingclass, or changing a token.- The house wrappers (
PageHeader,DataTable,RowActions, …) are in house-components.md; use them before composing primitives yourself. gap-*on flex and grid, neverspace-*.cn()for conditional classes.
What to use
| Need | Use |
|---|---|
| an action | Button, or buttonVariants() on an Inertia <Link> |
| a form | FieldGroup > Field > FieldLabel / control / FieldError (through FormField) |
| an input with an icon, affix or inner button | InputGroup + InputGroupAddon + InputGroupInput |
| one read-only field of a record | DetailCard in a grid, never a Table row |
| a timestamp | DateTimeValue |
| 2–7 exclusive choices | ToggleGroup, not Buttons with manual state |
| related checkboxes or radios | FieldSet + FieldLegend |
| a panel | Card with CardHeader / CardTitle / CardDescription / CardContent / CardFooter |
| a callout | Alert |
| an empty section | EmptyState; an empty table row is TableEmpty |
| a chip | Badge; a status is StatusBadge |
| a loading placeholder | Skeleton |
| a rule | Separator, never <hr> or a bordered div |
| a progress bar | Progress |
| a modal · a side panel · a confirmation | Dialog · Sheet · AlertDialog (through ConfirmAction) |
| a menu | DropdownMenu, items inside a DropdownMenuGroup |
| a searchable list or picker | Command, in a Popover or Dialog |
| progressive disclosure | Accordion (many rows) or Collapsible (one) |
| a tooltip | Tooltip (needs one TooltipProvider in the layout) |
| a chart | ChartContainer + Unovis (shadcn-vue's chart) |
Don't hand-roll a styled <button>, a chip <span>, a native <select>, a div-with-a-width
progress bar or a lone <hr>.
Composition rules the registry expects
- Items live in their group:
SelectItem→SelectGroup,DropdownMenuItem→DropdownMenuGroup,CommandItem→CommandGroup. TabsTriggerlives inTabsList, never directly inTabs.- Every
Dialog,SheetandDrawerhas a title (class="sr-only"if it should not show). Avataralways hasAvatarFallback.- Use the whole
Card: title and description inCardHeader, actions inCardFooter, not everything inCardContent. - No manual
z-indexon an overlay. They manage their own stacking. - Reka's
SelectItemrejects an empty value. A "no filter" option uses theALLsentinel (lists.md). - A
Selectwhose trigger draws its own content (instead ofSelectValue) setsposition="popper"on its content, or the list can open off-screen.
Layout classes
gap-*on a flex or grid, neverspace-x-*/space-y-*. One rhythm, set on the container.- The spacing scale:
gap-4between sections and cards,gap-2inside a group of buttons or a toolbar,gap-1between row-action icons,gap-6inside an auth card. size-*when width and height match:size-8, notw-8 h-8.truncate, not the three-property longhand. A record's value is never truncated.classis for layout: width, margin, grid placement. Colours and type come from variants and tokens.cn()for conditional classes, never a template-literal ternary. A component with visual variants declares them withcva, so the variant map is the one place a look is decided.- Field grids:
grid gap-4 sm:grid-cols-2in a form,grid auto-rows-min gap-4 sm:grid-cols-2 lg:grid-cols-3for record facts.
Keeping components/ui/ healthy
- Never hand-edit a file under
components/ui/. The CLI writes it, and re-adding a component replaces your edit while reporting only "Updated 1 file". Change a look through a token, aclassat the call site, a constant inlib/(assolidDestructive), or a wrapper. - Hardcoded English inside a registry component is overridden at the call site (a slot, an
aria-label), never in the file. - Every
npx shadcn-vue@latest addcan rewrite the stylesheet. Readgit diffon it after each one (theme.md). - Use
npx shadcn-vue@latest diffto see what upstream changed before re-adding anything; it should be clean for files you did not mean to touch. - Don't import
reka-uidirectly in your own code. shadcn-vue is built on it and its generated files import it, which is why it stays independencies. Reach for the shadcn-vue component. The exception isConfigProviderat the root of each layout (rtl-and-bidi.md). - One icon package. If a community registry component arrives importing another icon set, swap its icons before committing.