skills/design-system-create/SKILL.md
--- name: design-system-create description: Scaffold a design system in a React/Tailwind project — DESIGN_SYSTEM.md spec + typed tokens module + cva variants for core primitives (Button, Input, Card). Emits a `/styleguide` showcase route (role-gated to superadmin + staff if Clerk is wired, dev-only otherwise) so every theme/component/token is auditable in-browser. Use when a project has hand-rolled Tailwind for buttons/inputs/cards and needs a single source of truth, or just to add the styleguid
npx skillsauth add RonanCodes/ronan-skills skills/design-system-createInstall 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.
Lay down the four-layer contract that makes UI predictable:
@theme inline bridge — maps vars to utilities (bg-background etc.)src/design-system/tokens.ts) — TYPOGRAPHY, SPACING, RADIUS, ELEVATION, Zcva-based component variants — Button, Input, Card with state tables baked inDESIGN_SYSTEM.md at repo root — rules, state tables, review checklistThe skill generates these in that order, and leaves the project with zero hex values in component files.
/ro:design-system-create # interactive — inspect repo, propose layout (showcase on by default)
/ro:design-system-create --primitives=button,input,card
/ro:design-system-create --showcase # explicit opt-in (default anyway)
/ro:design-system-create --no-showcase # skip the /styleguide route
/ro:design-system-create --showcase-only # ONLY add /styleguide to an existing DS
/ro:design-system-create --dry-run # show what would be written, don't write
Run in parallel:
ls src/components/ui/ (shadcn layout) or ls src/components/ (alternative)cat tailwind.config.* 2>/dev/null || cat src/styles.css | head -40grep -rn '#[0-9a-fA-F]\{3,8\}' src/components/ 2>/dev/null | head -20 (hex leakage)grep -rn 'scale-\|rounded-\[' src/components/ 2>/dev/null | head -20 (likely DS violations)Determine:
If the project already has some of this (e.g. shadcn-ui scaffolded), the skill augments rather than overwriting. Never clobber an existing DESIGN_SYSTEM.md without asking.
Via AskUserQuestion, confirm:
translate-y-px + darker bg (recommended), or fade only?/styleguide page that renders every token + primitive? (default: yes — it's the cheapest way to keep the spec honest). If Clerk is detected, the route is gated to superadmin + staff; otherwise it's dev-only (404 when import.meta.env.PROD)If Tailwind v4, ensure src/styles.css has:
@import 'tailwindcss';
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
The vars themselves live per-theme in src/lib/themes/css/*.css (or wherever the project keeps them). Do not inline concrete colours here.
src/design-system/tokens.tsTyped, importable, autocomplete-friendly. Template:
export const TYPOGRAPHY = {
display: 'text-3xl md:text-4xl font-bold tracking-tight',
h1: 'text-2xl md:text-3xl font-bold tracking-tight',
h2: 'text-xl font-semibold',
h3: 'text-base font-semibold',
body: 'text-sm leading-relaxed',
bodyLg: 'text-base leading-relaxed',
caption: 'text-xs text-muted-foreground',
label: 'text-sm font-medium',
mono: 'font-mono text-xs',
} as const
export type TypographyToken = keyof typeof TYPOGRAPHY
export const SPACING = {
xs: '1', sm: '2', md: '3', lg: '4', xl: '6', xxl: '8',
} as const
export type SpacingToken = keyof typeof SPACING
export const RADIUS = {
none: 'rounded-none',
sm: 'rounded-sm',
md: 'rounded-md',
lg: 'rounded-lg',
xl: 'rounded-xl',
pill: 'rounded-full',
} as const
export const ELEVATION = {
flat: 'border border-border',
raised: 'border border-border shadow-sm',
floating: 'border border-border shadow-md',
overlay: 'border border-border shadow-xl',
} as const
export const Z = {
base: 0, dropdown: 40, overlay: 50, dialog: 60, toast: 70, tooltip: 80,
} as const
Each primitive follows the same state-table contract. Example for Button:
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium " +
"transition-all duration-150 disabled:pointer-events-none disabled:opacity-50 " +
"outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] active:translate-y-px",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80',
destructive: 'bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80',
outline: 'border bg-background hover:bg-accent hover:text-accent-foreground active:bg-accent/80',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70',
ghost: 'hover:bg-accent hover:text-accent-foreground active:bg-accent/80',
link: 'text-primary underline-offset-4 hover:underline active:text-primary/80',
},
size: {
default: 'h-9 px-4 py-2',
xs: 'h-6 gap-1 px-2 text-xs',
sm: 'h-8 gap-1.5 px-3',
lg: 'h-10 px-6',
icon: 'size-9',
},
},
defaultVariants: { variant: 'default', size: 'default' },
},
)
Invariants the generated variants enforce:
translate-y-px — never scales upMirror this for Input (hover border-foreground/30, focus-visible border-ring + ring, disabled opacity-50) and Card (Flat/Raised/Floating/Overlay elevation tiers).
DESIGN_SYSTEM.mdRoot of the repo. Must include:
The six core rules (lift verbatim unless the project has a well-reasoned deviation):
rounded-md must be identical in rest, hover, active, focus, disabled.scale-110 or a larger ring for active — that says "emphasised", which is a different concept (toggle).ring-ring/50, outline-none. Don't customise per variant.rounded-sm (badges), rounded-md (buttons/inputs), rounded-lg/xl (cards), rounded-full (pills). No rounded-[7px] one-offs.Emit a /styleguide page that renders every token and primitive so the system is auditable in-browser across themes. Default-on; use --no-showcase to skip.
Route path: /styleguide. Picked over /design-system and /showcase because it's the term frontend devs already search for, reads cleanly as a sidebar nav entry the day a real admin panel exists, and doesn't suggest the route is dev-only (it's role-gated in production by design, see below).
Where to write it:
src/routes/styleguide.tsx (route = /styleguide)app/styleguide/page.tsxsrc/pages/Styleguide.tsx + register in the router manuallyGating — role-based if Clerk is wired, dev-only otherwise:
Detect Clerk by looking for @clerk/tanstack-react-start in package.json AND a src/lib/auth/roles.ts file (the requireRole() helper emitted by /ro:clerk add-roles).
If both are present (preferred): wire the route to requireRole('superadmin', 'staff'). Production-safe — only the superadmin email + Clerk org:staff members can view. Members and signed-out visitors get a 404 (not a redirect — don't leak the existence of admin routes).
// src/routes/styleguide.tsx
import { createFileRoute } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { requireRole } from '@/lib/auth/roles';
const guardFn = createServerFn({ method: 'GET' }).handler(async () => {
return await requireRole('superadmin', 'staff');
});
export const Route = createFileRoute('/styleguide')({
beforeLoad: () => guardFn(),
component: StyleguidePage,
});
If Clerk is wired but src/lib/auth/roles.ts is missing: prompt the user to run /ro:clerk add-roles first (one command, takes <30s), then emit the gated route. Do NOT silently skip the gate — that's the failure mode where an unguarded admin surface ships to production.
If Clerk is not wired: fall back to a dev-only gate. The route 404s in production builds, renders in dev. This is the "scaffold without auth" path; the moment auth lands, swap to the role-gated form.
// src/routes/styleguide.tsx — no-auth fallback
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/styleguide')({
beforeLoad: () => {
if (import.meta.env.PROD) {
throw new Response('Not Found', { status: 404 });
}
},
component: StyleguidePage,
});
Rules for the showcase file:
src/lib/themes/index.ts exists. The toggle lives on-page so you can A/B both modes side-by-side without leaving the route.Required sections (in order):
superadmin / staff / dev-mode), light/dark toggle (mandatory), multi-theme picker (if themes exist)bg-background/text-foreground, bg-card/text-card-foreground, etc.) as labelled swatches. Names match the @theme inline bridge.TYPOGRAPHY.* key, token name on the left, rendered sample on the right (use "The quick brown fox…")RADIUS.* value with the key as captionELEVATION.* tieraria-pressed state toggled via local state, showing the recommended filled+bordered on-statearia-invalid, with-value (in a 2-col grid)SPACING.* value with px readoutDESIGN_SYSTEM.md (relative to repo host if known)Why it earns its keep:
/styleguide and eyeball every primitive in 30 secondsorg:staff in Clerk, share the /styleguide link, no extra access neededSee docs/showcase-template.tsx in the repo where this skill was developed (connections-helper src/routes/styleguide.tsx) for a canonical reference implementation.
Print what was written:
✅ Design system scaffolded
Created:
DESIGN_SYSTEM.md — spec + review checklist
src/design-system/tokens.ts — typed tokens
src/styles.css — @theme inline bridge (augmented)
src/components/ui/button.tsx — cva variants with state table
src/components/ui/input.tsx — matches button state treatment
src/components/ui/card.tsx — elevation tiers
src/routes/styleguide.tsx — /styleguide showcase (role-gated to superadmin+staff if Clerk wired, dev-only otherwise)
Next:
1. Visit /styleguide in dev to verify the scaffold renders cleanly across themes (sign in as superadmin or an org:staff member if Clerk is wired)
2. Run /ro:design-system-audit to find callers that need migrating
3. Review DESIGN_SYSTEM.md — deviate where your brand demands it
4. Commit the scaffold before starting migrations
shadcn/ui is the default component layer for every app on the stack. A custom Tailwind-token component layer is acceptable ONLY when all three hold at once: the app is already token-driven (a tokens.ts + theme bridge exists and components read from it), its tests pass, and shadcn primitives cannot pixel-match the design without churn. In every other case, take shadcn and restyle it via tokens and cva variants. A rival component framework (Chakra, MUI, Mantine, Ant) is never acceptable — that is a different decision from "hand-rolled token components" and this skill's opinions do not transplant onto it (it aborts, see Safety).
The practical test: if you are reaching for custom because shadcn "feels heavy", that is not one of the three conditions. Custom is earned by a token-driven codebase where shadcn would fight the existing primitives, not chosen up front. Decided once here so it is not re-argued per session (lekkertaal, 2026-06-06).
@theme inline bridge for Tailwind v4 — maps a small number of semantic vars to a much larger utility surface for free.DESIGN_SYSTEM.md, tokens.ts, or primitives without explicit confirmation/ro:commit for the "just scaffolded" commit/ro:design-system-audit — run after scaffolding to find callers that still violate the rules/ro:frontend-design — broader UI review skill; this one is narrower and more actionable/ro:coding-principles — simplicity principles that informed the "rules not laws" choice/ro:clerk add-roles — emits the requireRole() helper that /styleguide consumes when Clerk is wiredtesting
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".