Shade — ShadCN install guardrails
Most new Shade components start from a ShadCN install. The CLI is destructive by default — follow these guardrails.
Command
pnpm dlx shadcn@latest add <component-name>
(Use pnpm, not npx / yarn / bunx. Ghost is pnpm.)
Before running
- Be on a fresh branch. The CLI's diff is mixed in with your branch's changes otherwise.
- Check if the component already exists in
apps/shade/src/components/ui/. If it does, do not run the CLI against the repo — generate into a scratch repo and manually port the parts you want. The CLI will offer to overwrite, and you'd lose Shade customisations.
During the prompts
- If asked to overwrite an existing file: choose No. Always.
- If asked about path aliases: keep
@/.
After the file lands — required cleanup
Raw ShadCN output is not Shade-quality yet. Do all of these:
- Swap raw colours for semantic tokens. ShadCN ships things like
bg-white,border-gray-200,text-zinc-500. Replace withbg-surface-elevated,border-border-default,text-muted-foreground, etc. See theshade-tokens-not-hexskill for the inventory. - Remove any
dark:colour variants. Semantic tokens flip automatically. Seeshade-no-dark-variants. - Use the
@/alias for internal imports (@/lib/utils,@/components/ui/...) — not relative paths. - Ensure all four required states work: default, hover, focus-visible, disabled. (
focus-visible:only — neverfocus:.) - Trim props that hint at a specific surface. If a prop name reads like product workflow (
isMembersPage,layoutMode), it doesn't belong on a generic Component — extract a Pattern instead. - For form controls, drive border/background/focus through the
inputSurfacerecipe instead of duplicating the chrome. Seeshade-input-surface-recipe. - Add a sibling
<name>.stories.tsxif one isn't already there. Copy useful examples fromhttps://ui.shadcn.com/docs/components/<name>into stories. Seeshade-new-component. - forwardRef + className merge via
cn()— both are required.
What ShadCN gets wrong that Shade fixes
| ShadCN default | Shade convention |
|---|---|
bg-background dark:bg-background |
bg-background (token already flips) |
bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50 |
bg-surface-elevated text-foreground |
border border-input |
border border-control-border (form controls) or border-border-default (other chrome) |
Direct focus-visible:ring-2 focus-visible:ring-ring chrome on every input |
inputSurface('self') recipe |
Relative imports import {cn} from "../../lib/utils" |
@/ alias: import {cn} from '@/lib/utils' |
When NOT to use the CLI
- The component already exists in Shade — port from a scratch repo instead.
- You're building a Pattern (something Ghost-shaped like
KpiCard,PageHeader). ShadCN doesn't ship Patterns — write it from primitives + components. - It's a Recipe (pure class-string function). ShadCN doesn't ship Recipes.
Source of truth
Storybook → Overview / Contributing. This skill adds the agent-specific safety steps for running the destructive ShadCN CLI.