| name | ds-install |
| description | Bootstrap the SaaS Panel design system into a React + TypeScript + Tailwind project — installs dependencies, writes the color/typography tokens, patches the Tailwind config, adds the shadcn/ui primitives with the system's diffs, and copies the system's components, shell and examples. Use when a project has not adopted the design system yet, when tokens like --primary-glow are missing, or when the user asks to "install", "set up", "bootstrap" or "apply" the design system to a new or existing codebase. |
| license | MIT |
| compatibility | Target project must use React 18+, TypeScript and Tailwind CSS 3. Works with Vite, Next.js and Remix. Node 18+ and a package manager (npm, pnpm, bun or yarn) must be available. |
| metadata | {"version":"1.0.0","part-of":"saas-panel-design-system"} |
Install the SaaS Panel design system
Bootstraps the system into a target project. Run this before building the
first screen. After it finishes, use the saas-panel-design-system skill for
the screen patterns.
Locate the package
All source files live in the sibling skill directory:
../saas-panel-design-system/assets/
In Claude Code that resolves to ${CLAUDE_SKILL_DIR}/../saas-panel-design-system/assets/.
If the relative path fails (different install layout), find it:
find ~ -type d -path "*skills/saas-panel-design-system/assets" 2>/dev/null | head -3
Do not proceed without the real files. Never retype tokens.css or a
component from memory — copy the packaged file. Retyping is how drift starts.
Step 1 — inventory before touching anything
cat package.json
ls tailwind.config.* postcss.config.* 2>/dev/null
ls src/index.css src/app/globals.css app/globals.css 2>/dev/null
ls src/components/ui 2>/dev/null | head -20
cat components.json 2>/dev/null
grep -rn "primary-glow" src 2>/dev/null | head -3
Report to the user, in one short block:
- stack detected (Vite/Next/Remix, package manager, Tailwind version);
- what is missing vs. present;
- which existing files this install will modify or overwrite;
- whether shadcn primitives already exist (they will be patched, not replaced
wholesale — see Step 5).
If any existing file would be overwritten, ask for confirmation before
writing. A CSS file with the user's own rules must be merged, not replaced.
Step 2 — dependencies
Required:
npm i class-variance-authority clsx tailwind-merge tailwindcss-animate lucide-react
Add only what the project will actually use:
| Need | Package |
|---|
| Dark mode toggle | next-themes |
| Routing (shell, hub, breadcrumbs) | react-router-dom |
| Server state in the examples | @tanstack/react-query |
| Forms | react-hook-form zod @hookform/resolvers |
| Charts (dashboards) | recharts |
Use the project's package manager (pnpm add, bun add, yarn add).
Step 3 — cn()
Create src/lib/utils.ts if absent:
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Step 4 — tokens and Tailwind config
-
Copy assets/tokens.css into the project's global stylesheet
(src/index.css, src/app/globals.css, …).
- Fresh project: copy the file as-is.
- Existing stylesheet: merge. Keep the project's own rules; replace the
:root / .dark token blocks. Do not end up with two @tailwind base.
-
Merge assets/tailwind.config.example.ts into the project's Tailwind config.
The parts that must survive: darkMode: ['class'], the whole colors map
(including primary.glow), borderRadius reading --radius, the
fontFamily tokens, and the tailwindcss-animate plugin.
-
Decide the density switch with the user:
html:has(.app-shell) { font-size: 90%; }
Keep it (dense panel, matches the system's proportions) or drop it
(everything renders ~10% larger). If kept, the shell root element must carry
the app-shell class. Say which one you chose.
-
Wire the theme provider if next-themes was installed:
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
Verify before moving on: a bg-primary/10 div renders a translucent brand
tint, and toggling .dark on <html> flips the palette. If /10 renders
nothing, the tokens were written with hsl() or commas — fix the triplet format.
Step 5 — shadcn primitives
If the project has no components/ui:
npx shadcn@latest init
npx shadcn@latest add button card input textarea select table dialog alert-dialog \
badge popover command tooltip switch label form separator avatar skeleton toast \
collapsible checkbox toggle-group
Then overwrite these seven with the packaged, already-patched versions:
assets/primitives/{button,card,input,textarea,select,table,dialog}.tsx
→ src/components/ui/
If the project already had customized primitives, do not blind-overwrite:
diff each one, apply the system's changes (listed in
../saas-panel-design-system/references/04-primitivos-ui.md §"diffs"), and keep
the project's own additions. Show the diff summary to the user.
Step 6 — the system's components
Copy from assets/components/ into src/, adjusting import paths to the
project's alias:
components/ → src/components/
PageHeader.tsx StatsGrid.tsx ListCard.tsx SortableHead.tsx PageSubnav.tsx
HubPage.tsx DetailModal.tsx InlineField.tsx ResizableDialog.tsx
ReportsNav.tsx ReportTable.tsx DashboardKit.tsx ThemeToggle.tsx
PageSkeletons.tsx ProjectSelect.tsx (ProjectSelect = combo template)
table-pagination.tsx → src/components/ui/ (it is imported as a ui module)
useAutoPageSize.ts useSortedRows.ts useDebounce.ts useModalWidth.portable.ts
→ src/hooks/
sort.ts csv.ts → src/lib/
Copy only what the project needs, but keep each group whole — ListCard without
table-pagination + useAutoPageSize is broken.
Known adaptations (do these, do not skip):
useModalWidth.portable.ts → rename to useModalWidth.ts. It takes userId
as a parameter; wire it to the project's auth (or pass undefined).
DetailModal.tsx and ResizableDialog.tsx import @/hooks/useModalWidth —
they must pass userId through.
ProjectSelect.tsx calls a useProjects() hook internally. Either point it
at the project's equivalent hook, or refactor it to receive items as a prop.
It is the template for every searchable combo.
ReportTable.tsx imports useToast from @/hooks/use-toast (shadcn toast)
and downloadCsv from @/lib/csv. Its ReportRow type is local and generic
— tighten it once the data shape is known.
DashboardKit.tsx needs recharts and the --primary-glow token.
ThemeToggle.tsx needs next-themes + the tooltip primitive.
Step 7 — shell (optional, only for a new panel)
assets/shell/ has a brand-neutral AppShell, AppSidebar, Breadcrumbs and
SidebarContext. Install them when the project needs the panel chrome:
<SidebarProvider>
<AppShell
menu={MENU} /* areas + items + sub-items */
routeLabels={ROUTE_LABELS} /* localized label per path, for breadcrumbs */
sidebarBrand={{ icon: <Logo />, name: 'Product' }}
sidebarBackground="linear-gradient(180deg, …)" /* YOUR brand */
headerActions={<><ThemeToggle /><UserMenu /></>}
>
<Routes>…</Routes>
</AppShell>
</SidebarProvider>
Two rules that come with the shell:
- Sub-items of a section declare
?tab= explicitly, including the first one
(/leads?tab=list) — the active matcher compares URLs with query strings by
exact equality, so omitting it breaks the highlight.
- Because the shell renders the breadcrumb, no page gets a "Back" button.
Step 8 — verify, then report
Run whatever the project has:
npx tsc --noEmit
npm run lint
npm run build
Then confirm visually (or ask the user to):
Finish with a short report: what was installed, what was skipped and why, which
files were modified, and the one next step — build the first screen with the
saas-panel-design-system skill.
Step 9 — make the rules stick
Append to the project's CLAUDE.md / AGENTS.md (create if absent):
## Design system
This project uses the SaaS Panel design system. Before building or restyling any
UI, load the `saas-panel-design-system` skill and follow its patterns.
Non-negotiable: details = `<DetailModal>` (never a drawer); page sub-navigation =
`<PageSubnav>` (never `<Tabs>`); list screens = PageHeader + StatsGrid + filter
Card + `<ListCard>` with adaptive pagination (never a fixed PAGE_SIZE); growable
selects = searchable combo; destructive actions = `AlertDialog`; loading/error/
empty states always covered; no loose hex colors.
Without this, the next session drifts from the system on the first screen.