skills/design-system-bootstrap/SKILL.md
Guides the practical path from 'we have no design system' or 'we have a mess' to a real, functional design system. Component inventory, design token extraction, headless UI adoption (Radix, React Aria, Headless UI), migration order of operations, and the assessment-to-rollout pipeline. Activate on 'design system from scratch', 'bootstrap design system', 'no design system', 'design system migration', 'headless UI adoption', 'component audit', 'design token extraction', 'design system strategy'. NOT for building specific components once a system exists (use design-system-creator), not for generating token files/configs (use design-system-generator).
npx skillsauth add curiositech/windags-skills design-system-bootstrapInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The practical guide to going from "we have nothing" (or "we have a mess") to a functional design system. This is not about building a component library from scratch in isolation — it is about assessing what exists, extracting what works, choosing the right foundations, and migrating incrementally without breaking production.
Use for:
Do NOT use for:
Before building anything, understand what you have.
Run a systematic audit of every UI component in the codebase.
Automated Discovery:
# Find all component files
find src -name "*.tsx" -o -name "*.jsx" | \
xargs grep -l "export.*function\|export.*const.*=" | \
sort
# Find all CSS/styling approaches in use
find src -name "*.css" -o -name "*.scss" -o -name "*.module.css" | wc -l
grep -r "styled(" src --include="*.tsx" -l | wc -l # styled-components
grep -r "className=" src --include="*.tsx" -l | wc -l # CSS classes
grep -r "sx={{" src --include="*.tsx" -l | wc -l # MUI sx prop
grep -r "css=" src --include="*.tsx" -l | wc -l # Emotion css prop
Manual Inventory Spreadsheet:
| Component | Variants Found | Files | Styling Method | Accessible? | Notes | |-----------|---------------|-------|---------------|-------------|-------| | Button | 7 different styles | 12 files | Mixed CSS + inline | Partial | 3 lack focus states | | Modal | 3 implementations | 5 files | styled-components | No | No trap focus, no ESC close | | Input | 4 styles | 8 files | CSS modules | Partial | Inconsistent validation | | Dropdown | 2 custom, 1 library | 4 files | Mixed | No | No keyboard nav |
Extract the implicit design decisions already embedded in the codebase.
Color Extraction:
# Find all hex colors in use
grep -roh '#[0-9a-fA-F]\{3,8\}' src --include="*.tsx" --include="*.css" | \
sort | uniq -c | sort -rn | head -30
# Find all rgb/rgba/hsl values
grep -roP 'rgba?\([^)]+\)|hsla?\([^)]+\)' src --include="*.tsx" --include="*.css" | \
sort | uniq -c | sort -rn | head -20
Spacing Extraction:
# Find padding/margin values
grep -roP '(padding|margin|gap):\s*[^;]+' src --include="*.css" | \
sort | uniq -c | sort -rn | head -20
# Tailwind spacing classes in use
grep -roP '(p|m|gap|space)-[a-z0-9\[\]]+' src --include="*.tsx" | \
sort | uniq -c | sort -rn | head -30
Typography Extraction:
# Font sizes
grep -roP 'font-size:\s*[^;]+' src --include="*.css" | \
sort | uniq -c | sort -rn
# Font families
grep -roP "font-family:\s*[^;]+" src --include="*.css" | \
sort | uniq -c | sort -rn
Rate your current state (1-5) on each dimension:
| Dimension | 1 (Crisis) | 3 (Manageable) | 5 (Solid) | |-----------|-----------|----------------|-----------| | Consistency | Every component styled differently | Some shared patterns | Unified visual language | | Accessibility | No ARIA, no keyboard nav | Partial, inconsistent | Full WCAG AA compliance | | Token usage | Hardcoded values everywhere | Some CSS variables | Full token system | | Component reuse | Copy-paste culture | Some shared components | Single-source library | | Documentation | None | README per component | Full Storybook |
Score 5-10: You are in emergency mode. Start with tokens + one component. Score 11-18: You have something to work with. Systematic migration possible. Score 19-25: You need polish, not a rewrite.
Tokens are the foundation everything else builds on. Define them before touching components.
Minimum Viable Token Set:
:root {
/* Colors — semantic, not descriptive */
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-text-muted: #9ca3af;
--color-text-inverse: #ffffff;
--color-bg-primary: #ffffff;
--color-bg-secondary: #f9fafb;
--color-bg-tertiary: #f3f4f6;
--color-border-default: #e5e7eb;
--color-border-strong: #d1d5db;
--color-accent-primary: #6366f1;
--color-accent-primary-hover: #4f46e5;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* Spacing — constraint scale */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-24: 6rem; /* 96px */
/* Typography */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
/* Border radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12);
/* Transitions */
--transition-fast: 0.1s ease;
--transition-default: 0.2s ease;
--transition-slow: 0.3s ease;
}
Tailwind Config Equivalent:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
accent: {
DEFAULT: '#6366f1',
hover: '#4f46e5',
},
},
fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans],
mono: ['JetBrains Mono', ...defaultTheme.fontFamily.mono],
},
},
},
};
This is the highest-leverage decision. Choose wrong, and you rewrite everything later.
| Criteria | Radix UI | React Aria | Headless UI | Ark UI | Base UI (MUI) | |----------|----------|------------|-------------|--------|---------------| | Component count | 28 | 43 | 10 | 30+ | 20+ | | API style | Compound components | Hooks + Components | Compound components | Compound components | Hooks | | Accessibility | Very good | Best-in-class | Good | Very good | Good | | Styling freedom | Full (unstyled) | Full (unstyled) | Full (unstyled) | Full (unstyled) | Full (unstyled) | | React Server Comp. | Partial | Yes | Partial | Yes | Partial | | Animation support | data-state attrs | Built-in transitions | Transition component | data-state attrs | Minimal | | Bundle size (Dialog) | ~8KB | ~12KB | ~5KB | ~10KB | ~7KB | | Ecosystem | shadcn/ui, Radix Themes | Adobe Spectrum | Tailwind ecosystem | Panda CSS | Joy UI | | Documentation | Excellent | Very good | Good | Good | Good | | Framework support | React only | React only | React + Vue | React + Vue + Solid | React only |
Do you need Vue/Solid support?
├── Yes → Ark UI or Headless UI
└── No → Continue
│
Do you need maximum accessibility (gov, enterprise, WCAG AAA)?
├── Yes → React Aria
│ (strictest ARIA implementation, Adobe-backed)
└── No → Continue
│
Do you want the largest pre-built ecosystem (shadcn/ui)?
├── Yes → Radix UI
│ (shadcn/ui is built on Radix, largest component community)
└── No → Continue
│
Do you need the fewest dependencies possible?
├── Yes → Headless UI (smallest, Tailwind team)
└── No → Radix UI (safe default, best DX)
shadcn/ui is not a library — it is a collection of copy-paste components built on Radix + Tailwind. It deserves special consideration:
Pros:
npx shadcn-ui@latest add button dialog dropdown-menuCons:
npm update)Verdict: If you are starting from zero with React + Tailwind, shadcn/ui is the fastest path to a functional design system. You can customize everything later because you own the source.
# Bootstrap shadcn/ui into an existing project
npx shadcn-ui@latest init
# Add components as needed
npx shadcn-ui@latest add button input card dialog dropdown-menu \
tabs tooltip popover command separator badge
Do NOT build all components at once. Follow this priority order based on usage frequency and impact.
| Component | Why First | Headless Primitive | |-----------|----------|-------------------| | Button | Used everywhere, most inconsistency | None needed (semantic HTML) | | Input + Label | Every form, biggest a11y gap | React Aria: useTextField | | Dialog/Modal | High visibility, hardest to get right | Radix: Dialog, React Aria: useDialog | | Dropdown Menu | Navigation, actions, context menus | Radix: DropdownMenu |
| Component | Why | Headless Primitive | |-----------|-----|-------------------| | Select | Form-critical, notoriously hard | Radix: Select, React Aria: useSelect | | Tabs | Layout organization | Radix: Tabs | | Toast/Notification | Feedback system, often inconsistent | Radix: Toast | | Card | Content container, used everywhere | None needed (styled div) |
| Component | Why | Headless Primitive | |-----------|-----|-------------------| | Tooltip | Contextual help | Radix: Tooltip | | Popover | Rich contextual content | Radix: Popover | | Toggle/Switch | Settings, preferences | Radix: Switch | | Checkbox + Radio | Form elements | Radix: Checkbox, RadioGroup | | Accordion | Content disclosure | Radix: Accordion |
Badge, Avatar, Separator, Slider, Progress, Skeleton, Command Palette, Date Picker, Combobox.
Do not rewrite. Wrap, replace, deprecate.
Step 1: Create new <Button> component in design system
Step 2: Import it alongside old buttons
Step 3: Replace old buttons one page at a time
Step 4: When all old buttons replaced, delete old code
Step 5: Repeat for next component
Implementation:
// 1. New component in your design system
// packages/ui/src/Button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-accent-primary text-white hover:bg-accent-hover',
secondary: 'border border-border-default bg-transparent hover:bg-bg-secondary',
ghost: 'hover:bg-bg-secondary',
destructive: 'bg-error text-white hover:bg-red-600',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant, size, className, ...props }, ref) => (
<button
ref={ref}
className={buttonVariants({ variant, size, className })}
{...props}
/>
)
);
// 2. Migration wrapper for gradual adoption
// src/components/LegacyButtonAdapter.tsx
import { Button } from '@your-org/ui';
// Map old prop API to new
export function LegacyButtonAdapter({ type, ...props }) {
const variantMap = {
primary: 'primary',
secondary: 'secondary',
danger: 'destructive',
link: 'ghost',
};
return <Button variant={variantMap[type] || 'primary'} {...props} />;
}
// .eslintrc.js — warn on old component imports
module.exports = {
rules: {
'no-restricted-imports': ['warn', {
paths: [
{
name: './components/OldButton',
message: 'Use <Button> from @your-org/ui instead.',
},
{
name: './components/OldModal',
message: 'Use <Dialog> from @your-org/ui instead.',
},
],
}],
},
};
Track progress per component per page:
Component: Button
Total instances: 47
Migrated: 32/47 (68%)
├── /dashboard ████████████████████░░░░ 85% (17/20)
├── /settings ████████████████████████ 100% (8/8)
├── /onboarding ██████░░░░░░░░░░░░░░░░░░ 25% (3/12)
└── /billing █████████████░░░░░░░░░░░ 57% (4/7)
npx storybook@latest init
Every component needs at minimum:
Track these to know if the design system is working:
| Metric | Target | How to Measure | |--------|--------|---------------| | Component coverage | 80%+ of UI uses DS components | grep for DS imports vs raw HTML | | Token compliance | 90%+ of colors/spacing from tokens | grep for hardcoded values | | Accessibility score | 0 axe violations per page | Storybook a11y addon, Lighthouse | | Developer satisfaction | 4+/5 in quarterly survey | Internal survey | | Time to build new page | 50% reduction | Track sprint velocity |
Designers want pixel-perfect Figma components before code exists. This creates a waterfall where code never matches design and both drift. Build code and Figma in parallel. Code is the source of truth. Figma is a communication tool.
Creating a <Box> component that wraps every possible CSS property. Creating a <Text> component with 47 props. Start concrete, abstract when patterns emerge. You cannot predict your abstractions before building three real pages.
Building a beautiful new design system that sits in a /packages/ui folder unused because there is no plan for migrating existing code. Plan migration before building components. The best design system is the one that gets adopted.
Picking a headless UI library because it was trending on Twitter. Choose by your team's constraints: framework, accessibility requirements, bundle budget, component coverage needs.
Jumping straight to building components without understanding what already exists. You will build things that already exist in three variants, miss patterns that work, and create migration confusion. Phase 0 is not optional.
A single 2000-line theme file with every possible value. Instead, use layered tokens: primitive (raw values) → semantic (purpose-named) → component (component-specific overrides).
Taking MUI/Chakra/Ant Design and wrapping every component with your own props "in case we switch later." You will not switch. Use the library directly or build your own. The wrapper adds bugs and zero value.
A "design system team" that builds components in isolation while product teams wait. Design system work should be embedded in product work. Build the component when a product team needs it, not before.
Mandating design system usage before the system is good enough to want. If developers prefer writing raw CSS over using your components, the components need to be better, not the developers.
Design research based on:
tools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.