shadcn in client/admin
client/admin/src/components/ui/** is vendored. The shadcn CLI owns every file in it. Two rules follow:
- Get components from the CLI. Never hand-write a
ui/file. Never paste one from ui.shadcn.com. - Never edit a
ui/file. Customize by composition. If a change looks impossible without editingui/, stop and ask the user.
client/dashboard does not obey rule 2 today. Its ui/ directory holds custom files. Do not copy that habit into client/admin.
Commands
Run every shadcn command from client/admin. The CLI reads the nearest components.json. From the repo root it resolves the wrong package.
| Goal | Command |
|---|---|
| Add a component | aube dlx shadcn@latest add <name> |
| Preview before you add | aube dlx shadcn@latest add <name> --dry-run |
| Show the project config | aube dlx shadcn@latest info |
| Read the component docs | aube dlx shadcn@latest docs <name> |
| Compare with upstream | aube dlx shadcn@latest add <name> --diff |
Do not use npx, npm, yarn, or pnpm dlx. This repo uses aube.
Read docs <name> before you decide that a variant or a sub-component is missing.
After you add a component
Run these three commands, in this order:
hk fix
aube run -F admin type-check
aube run -F admin lint:oxlint
hk fix is not optional. Every file the shadcn CLI writes fails oxfmt. hk fix reformats it.
Do not run aube run -F admin lint or aube run -F admin lint:format. Both fail before they lint. oxfmt is installed only in the root node_modules/.bin, so the package script stops at sh: 1: oxfmt: not found.
--dry-run lists the dependencies a component pulls in. When the CLI adds a dependency to client/admin/package.json, check pnpm-workspace.yaml for a catalog: entry. Use catalog: when one exists.
Customize by composition
Use the first option that works:
- Props and
className. Layout and spacing only. - Built-in variants.
<Button variant="ghost" size="sm">. - A wrapper component in
client/admin/src/components/, never inui/.ConfirmDialog.tsxwrapsDialog.data-table.tsxwraps the table primitives. Follow that pattern and name the wrapper for what it is. - A
cvavariant object in your own file, applied throughcn().
Never override a component's colors with className. Use the semantic tokens: bg-primary, text-muted-foreground, border-border.
Read the --diff output correctly
--diff compares the local file with upstream. Two differences are expected noise:
- "Formatting-only changes (spacing, quotes, semicolons)" — caused by
hk fix. Every admin component reports this. It is not drift. - A
"use client"line added or removed —components.jsonsets"rsc": falseand admin is a Vite SPA.
Anything else is real drift. Report it to the user. Do not overwrite it silently.
--diff prints at most 5 files per run. Pass one component at a time when you need the full list.
Common mistakes
- Hand-writing
ui/card.tsxbecause it is short. Run the CLI. - Editing
ui/button.tsxto add a variant. Wrap it, or ask the user. - Running the CLI from the repo root, then finding the file in
client/dashboard. - Passing
--overwriteto clear a conflict. It discards local state. Run--difffirst and read it. - Skipping
hk fix, then failing CI on formatting.