一键导入
bm-design-system
// Guides you through creating and implementing a design system for your application.
// Guides you through creating and implementing a design system for your application.
| name | bm-design-system |
| description | Guides you through creating and implementing a design system for your application. |
You are guiding a builder through scaffolding a complete design system into a React + Tailwind v4 codebase. The output is a single-page reference at /admin/design-system that previews and documents every primitive, plus a small set of reusable shadcn-style components, plus instructions appended to AGENTS.md (or CLAUDE.md) so future agents always defer to the design system instead of drifting.
The user wants a complete, opinionated design system scaffolded into their app with as few decisions as possible. Colors and fonts ship with locked defaults — don't ask the user to pick them. Whenever a technical concept appears in a phase that does take input (route, scope, migration), briefly explain it in plain language before asking.
Use the AskUserQuestion tool for decisions with discrete options. The user is likely on mobile — tappable options beat typing. For free-form input (custom hex codes, custom font names) use a normal chat message.
One decision at a time, in sequence. Walk through phases in order. Lock each phase before moving to the next.
Keep your prose tight. Short framings, no preamble. The user is making decisions, not reading essays.
The design system is opinionated, not exhaustive. Ship a small, useful, beautiful set of primitives with locked defaults. The user can extend by editing the scaffolded files or re-running the skill later.
@theme directive in CSS instead of tailwind.config.js.)Before talking to the user, inspect the codebase silently and form a picture.
Framework detection (in order — first match wins):
vite.config.ts / vite.config.js / vite.config.mts exists at repo root.next.config.* exists AND app/ directory exists.next.config.* exists AND pages/ directory exists, no app/.Gemfile contains inertia-rails (or inertia_rails) AND app/frontend/ or app/javascript/pages/ exists.Gemfile contains react_on_rails.Tailwind v4 check:
@import "tailwindcss" in any CSS file under common entry paths (src/, app/, app/javascript/, app/frontend/).@tailwindcss/vite, @tailwindcss/postcss, or tailwindcss@^4 in package.json.tailwind.config.js, @tailwind base;), stop: print "This skill requires Tailwind CSS v4 or later. Please upgrade first — see https://tailwindcss.com/docs/upgrade-guide — and re-run." and exit.Existing-system detection (sets first-run vs re-run mode):
/admin/design-system.src/components/ui/ (or framework-equivalent) already exist?@theme block?bm-design-system:start marker in AGENTS.md or CLAUDE.md?If any of those are true → re-run mode. Otherwise → first-run mode.
Agent instruction file:
AGENTS.md at repo root.CLAUDE.md at repo root.State a brief summary of what you found before moving on. Example:
Detected: Vite + React + Tailwind v4. No existing design system.
CLAUDE.mdpresent,AGENTS.mdabsent. I'll scaffold from scratch.
Default route: /admin/design-system.
Use AskUserQuestion:
Use /admin/design-system (recommended), Use /design-system (no admin prefix), Use /styleguideIf the user picks Other and provides a custom path, accept it (must start with /).
Save the chosen route as routePath for downstream phases.
Colors and fonts ship locked. Do not ask the user to pick them. Announce what's coming in one short summary, then move on:
Going with these defaults — the design system page will preview them all and you can edit the scaffolded CSS later if you want to tweak:
- Display font: Inter
- Body font: DM Sans
- Accent: Cyan (
#0891b2)- Signal: Amber light (
#fcd34d) — same shade in light and dark- Neutral: Slate
Use the values below for substitution in Phase 4. Don't ask the user to confirm — just proceed.
| Role | Family | Google Fonts import URL | CSS stack |
|---|---|---|---|
| display | Inter | https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap | 'Inter', ui-sans-serif, system-ui, sans-serif |
| body | DM Sans | https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap | 'DM Sans', ui-sans-serif, system-ui, sans-serif |
Derived from accent #0891b2, signal #fcd34d (light shade pinned in both modes), and the Slate neutral scale, per the rules in references/derive-palette.md:
| Token | Light | Dark |
|---|---|---|
| page | #ffffff | #020617 |
| surface | #f8fafc | #0f172a |
| hairline | #e2e8f0 | #1e293b |
| ink-body | #334155 | #e2e8f0 |
| ink-display | #0f172a | #f8fafc |
| ink-muted | #64748b | #94a3b8 |
| accent | #0891b2 | #0891b2 |
| accent-faded | #e1f2f6 | #031f33 |
| accent-darker | #0e7490 | #0e7490 |
| signal | #fcd34d | #fcd34d |
| signal-faded | #fffaea | #2f2b21 |
| signal-darker | #b45309 | #b45309 |
Save these as palette and fonts for Phase 4.
Skip this phase on first run.
If re-run mode, after Phase 2 ask:
Refresh tokens to current defaults, Add new sections to the page, Full re-scaffold (overwrites everything inside the bm-design-system markers)If "Add new sections", ask which section IDs to add (free-form, comma-separated against the canonical section list below).
If "Full re-scaffold", confirm with a second AskUserQuestion: Yes, overwrite, Cancel.
This is the "do the work" phase. Don't show drafts; just write the files. The defaults are locked and the user already confirmed the route.
Pick the path map for the detected framework. The canonical Vite map is:
| Logical path | Vite target |
|---|---|
| route page | src/admin/design-system/page.tsx |
| design-system components | src/components/design-system/*.tsx |
| ui primitives | src/components/ui/{button,input,label,dialog}.tsx |
| utils | src/lib/utils.ts |
| design-system stylesheet | src/styles/design-system.css |
| entry CSS (where to add the import) | the file containing @import "tailwindcss" |
Per-framework overrides:
app/admin/design-system/page.tsx; everything else at components/..., lib/..., styles/... (no src/ prefix unless src/ exists).pages/admin/design-system.tsx.app/frontend/pages/admin/design-system.tsx (or app/javascript/pages/... if that's the pattern); update Inertia routes file accordingly.src/components/... if src/ exists, otherwise the project root, and print a manual route registration snippet.(See also Phase 4e for the theme boot script.)
For each template under references/, write to its mapped target. Substitute these tokens (string-replace) at write time:
| Token in template | Replacement |
|---|---|
__ROUTE_PATH__ | the chosen routePath |
__HEADLINE_FONT__ | the headline font family name |
__HEADLINE_FONT_URL__ | the Google Fonts URL fragment for the headline font |
__BODY_FONT__ | the body font family name |
__BODY_FONT_URL__ | the Google Fonts URL fragment for the body font |
__COLOR_<TOKEN>_<MODE>__ | hex value, e.g. __COLOR_PAGE_LIGHT__ → #ffffff |
Files to write (sources under references/, plus their substitution behavior):
page/DesignSystem.tsx → components/design-system/DesignSystem.tsx (Next.js app-router clients also need a "use client" directive)page/{SidebarNav,SectionShell,CodeBlock,ColorSwatch}.tsx → components/design-system/page/palette.ts → components/design-system/palette.ts — substitute color + font tokens herepage/sections/**/*.tsx → components/design-system/sections/. Note: all 14 base-styles sub-sections live in a single BaseStylesSection.tsx (returns a fragment of 14 anchored SectionShells); the canonical section list maps to anchors, not files.components-ui/{button,button-dropdown,input,badge,dialog,checkbox,radio,select,rich-text-field,dropdown-menu,theme-toggle}.tsx → components/ui/ (skip a file if it already exists and the existing one already comes from this skill — check for a bm-design-system marker comment; otherwise ask the user before overwriting). Note: rich-text-field.tsx imports @milkdown/crepe and dropdown-menu.tsx imports @radix-ui/react-dropdown-menu — make sure the deps in 4f are installed before the user navigates to those sections, or the page will fail to render.styles/design-system.css → target stylesheet path; substitute color + font tokens herelib/utils.ts → lib/utils.ts only if missinglib/theme.ts → lib/theme.ts (the useTheme hook + helpers backing <ThemeToggle>)src/admin/design-system/page.tsx for Vite) that imports and renders DesignSystemAppend @import "./design-system.css"; (or correct relative path) to the project's existing entry CSS, immediately after @import "tailwindcss";. If the import is already present, skip. Wrap the import in bm-design-system:start / bm-design-system:end HTML-comment-style CSS block markers so re-runs can be non-destructive:
/* bm-design-system:start */
@import "./design-system.css";
/* bm-design-system:end */
Tailwind v4 source detection — Rails + Inertia and other split-source projects. Tailwind v4 normally auto-detects which files contain class names, but the auto-detection doesn't reliably cover projects where the source roots are split across multiple top-level directories — most notably Rails + Inertia, which has page components under app/javascript/pages/ and shared components under app/frontend/. If the entry CSS lives in app/javascript/entrypoints/ (or anywhere that doesn't naturally walk up to both roots), classes used in the other root will be silently dropped from the build and pages will render unstyled.
For Rails + Inertia (and any other framework where the source roots are split), add an explicit @source directive to the entry CSS that walks up to the closest common ancestor of all source roots. For Rails + Inertia with the entry CSS at app/javascript/entrypoints/application.css, that's:
@import "tailwindcss";
/* Tailwind v4 auto-detection doesn't reliably reach both `app/javascript/`
and `app/frontend/` in this Rails+Inertia setup, so declare them explicitly. */
@source "../../**/*.{ts,tsx,js,jsx}";
/* bm-design-system:start */
@import "./design-system.css";
/* bm-design-system:end */
Place the @source line between the tailwindcss import and the design-system import. For other frameworks (Vite with everything under src/, Next.js with everything under app/), Tailwind's auto-detection works and an explicit @source is unnecessary — skip it.
Use the corresponding snippet under references/routing/. Edit the user's router file in place when the framework supports clean detection (Vite + react-router-dom: look for the <Routes> block; Next.js: file-based, no edit needed). Otherwise print the snippet and tell the user where to paste it.
Two snippets need to land inside the app's HTML layout <head>, before any <script> that loads React / hydration code (Vite tags, Inertia entrypoint, Next.js scripts, etc.):
<link> tagsThe @theme block defines --font-display: "Plus Jakarta Sans", … and --font-sans: "DM Sans", …, but those CSS variables only resolve to the right typeface if the font files are actually loaded. Don't load fonts via @import url(...) inside design-system.css — bundlers inline that file mid-bundle, after Tailwind's rules. CSS spec says @import must come before any other rules, so browsers silently drop those imports and the page renders in the system fallback (ui-sans-serif).
Install the link-tag snippet from references/styles/font-link-tags.html into the layout's <head>. Substitute __HEADLINE_FONT_URL__ and __BODY_FONT_URL__ with the URLs picked in Phase 2. When both fonts are Google Fonts, prefer collapsing them into a single &family=… stylesheet for one fewer request.
The <ThemeToggle> primitive persists the user's choice to localStorage["bm-ds-theme"], but the React hook that reads it (useTheme in lib/theme.ts) only fires when a component using it actually mounts. In real apps the toggle commonly lives inside a portal-rendered dropdown that doesn't mount until the user opens it — so without a boot-time script, every full page load renders in light mode regardless of the saved preference.
Install the inline script from references/styles/theme-boot-script.html into the same <head>. It reads the same storage key and resolution rules as lib/theme.ts, then adds (or omits) the .dark class on <html> synchronously before paint.
app/views/layouts/application.html.erb, immediately above vite_javascript_tag / vite_typescript_tag.app/layout.tsx's <head> (font links via next/font or plain <link> tags; theme boot via <Script id="theme-boot" strategy="beforeInteractive">{...}</Script> or a dangerouslySetInnerHTML script).pages/_document.tsx, inside <Head> of the custom Document.index.html, inside <head> before <script type="module" src="/src/main.tsx"></script>.Wrap each inserted block in bm-design-system:start / bm-design-system:end HTML comment markers so re-runs can replace them idempotently.
Inspect package.json. For any of the following that are missing, append them to a single install command and print it (do not run npm/yarn yourself):
@radix-ui/react-dialog@radix-ui/react-dropdown-menuclass-variance-authorityclsxtailwind-mergelucide-react@milkdown/crepe@milkdown/core@milkdown/reactExample:
Run this to install missing deps:
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu class-variance-authority clsx tailwind-merge lucide-react @milkdown/crepe @milkdown/core @milkdown/react
Open AGENTS.md if it exists, else CLAUDE.md if it exists, else create AGENTS.md. If both exist, update both the same way so they stay in sync.
Before appending the managed block, read the file end-to-end and look for any pre-existing instructions about UI, CSS, design, styling, color, typography, components, or frontend conventions that live outside a bm-design-system:start/end block. Examples of what to flag:
bg-blue-600 for primary buttons"bg-gray-50")These will conflict with — or quietly override — the new design system if left in place. Resolve every conflict found:
__ROUTE_PATH__ for visual conventions."Make the edits in place, briefly note in chat what you removed or rewrote, then proceed to 5b.
Append (or replace, if the markers already exist) the block from references/agent-instructions.md. The block is delimited by HTML comments:
<!-- bm-design-system:start -->
…content from references/agent-instructions.md, with __ROUTE_PATH__ substituted…
<!-- bm-design-system:end -->
If the file is being created from scratch, also include a one-line top-level title above the block.
After the scaffold is in place, do a quick scan for user-facing UI that already exists in the codebase and was not written using the design system. The point is to surface the migration opportunity — not to do it now.
Scan the framework's standard UI locations (skip the route page you just wrote, and skip everything under components/design-system/, components/ui/, and lib/):
src/pages/**, src/routes/**, src/views/**, src/components/** (excluding src/components/ui/ and src/components/design-system/)app/**/page.tsx, app/**/layout.tsx, app/**/*.tsx (excluding app/admin/design-system/**), and components/** (excluding components/ui/ and components/design-system/)pages/**/*.tsx (excluding pages/admin/design-system.tsx), and components/**app/frontend/pages/** or app/javascript/pages/** (excluding the design-system page), plus the components directoryFor each candidate file, look for signals that it renders user-facing UI without the design system:
<button> / <a> / <input> / <select> / <form> elements with their own ad-hoc Tailwind classes (instead of <Button>, <Input>, <Select>, etc.)bg-white, bg-gray-50, bg-slate-100, text-gray-900, text-zinc-500, border-gray-200, raw hex via style={{...}}, etc.Group findings into broad buckets (don't list every file individually — just pattern + count + one or two example paths):
If the scan finds nothing meaningful (e.g. brand-new project, or everything already follows the system), say so in one sentence and skip 6b. Move on to Phase 7.
If there's UI to migrate, summarize what you found in chat — short, scannable. Then ask, using AskUserQuestion:
Yes, migrate everything now — agent walks the codebase file-by-file, replacing ad-hoc styles and elements with tokens and primitives. Confirm with the user before each substantial file change.Yes, but just <bucket> — narrow scope (e.g. only buttons, only pages). Ask which bucket if they pick this.Not now — skip; user can re-run later.If the user picks a migration option, proceed. Otherwise move on. Do not auto-migrate without explicit user opt-in — design-system migrations touch a lot of code and need explicit consent.
When migrating:
<button> with <Button> and pick the closest variant; same for <input>, <select>, <a> styled-as-button, <form> field wrappers.bg-white → bg-page, bg-gray-50 → bg-surface, text-gray-900 → text-ink-display, text-gray-500 → text-ink-muted, border-gray-200 → border-hairline, bg-blue-600 → bg-accent, etc. When the original color clearly isn't semantic (e.g. a brand-specific color used once), flag it in chat and ask before substituting..body-content.Print a short summary in chat:
Stop.
The page contains these sections, in this order, each with an anchor ID matching the slug:
branding/
colors #colors
typography #typography
structure/
shells #shells
main-navigation #main-navigation
sub-navigation #sub-navigation
page-headers #page-headers
body-content #body-content
footers #footers
elements/
iconography #iconography
buttons #buttons
button-dropdown #button-dropdown
forms #forms
badges #badges
toggle-buttons #toggle-buttons
listings #listings
modal #modal
dropdown-menu #dropdown-menu
callout #callout
base-styles/
heading-scale #heading-scale
h1 #h1
h2 #h2
h3 #h3
h4 #h4
h5 #h5
h6 #h6
anchor #anchor
paragraph #paragraph
strong #strong
lists #lists
list-item #list-item
blockquote #blockquote
hr #hr
label #label
Each section uses the <SectionShell> wrapper which renders five blocks in this order:
<hr>See references/derive-palette.md for the exact algorithm that turns the locked accent / signal / neutral defaults into the full ten-token light + dark palette.
id matching the canonical slug above for hash-link smooth scrolling.scroll-margin-top to clear the sticky header.<html> (Tailwind v4 @variant dark against .dark), persisted to localStorage["bm-ds-theme"].components/ui/ are the actual API the rest of the app imports.Generate a complete favicon set (SVG, PNGs, multi-resolution .ico, Apple touch icon) from a Lucide icon (default) or another source SVG, then wire the favicon meta tags into the codebase. Use when the user wants to set up a favicon, replace a placeholder favicon, or brand the app icon.
Use when the user wants to create a Product Requirements Document (PRD) for a new app, feature, or project. Guides the user through a structured interview and produces a complete PRD (as a visual single-file HTML page, a markdown file, or both) along with ready-to-build milestones.