PurposeLink to this section
Design systems built top-down often codify guesses. Systems grown from a real product codify what already works — but only if someone decides which repetitions are real patterns and which are coincidences. This skill finds candidates in the code, judges each one, and designs abstractions that carry behavior, not just styles.
When to UseLink to this section
- The codebase has several versions of the same component (three modals, five card styles, two table implementations)
- Values are hard-coded throughout (colors, spacing, z-indexes, durations)
- A shared component has grown so many props that no one knows how to use it
- Planning a component library or design system migration
- Deciding whether a piece of UI should become shared
Core PrinciplesLink to this section
- Extract from evidence. A pattern that appears three or more times with the same purpose is a candidate. Two instances is often a coincidence.
- Same look is not the same component. Abstract things that change for the same reasons; two things that happen to look alike today may diverge tomorrow.
- Encode behavior, not only style. The most valuable shared components carry the hard parts: states, focus, keyboard, async feedback, safeguards.
- Prefer composition over configuration. A component with slots/children scales; one with 30 boolean props does not.
- Wrong abstraction costs more than duplication. When in doubt, wait for more evidence.
- Adopt incrementally. New code uses the system; old code migrates when touched or when prioritized by impact.
WorkflowLink to this section
1. Scan for candidatesLink to this section
Look for:
| Signal | How to find |
|---|---|
| Duplicate components | Similar file names (Modal, Dialog, Popup); similar JSX/template structure; copied files with small diffs |
| Repeated markup patterns | Same element structure + class combinations appearing across features |
| Repeated behavior | Hand-rolled loading/empty/error rendering; custom dropdowns; repeated confirm-delete logic; repeated form field + label + error wrappers |
| Hard-coded values | Raw hex/rgb colors, pixel values, z-index numbers, durations, breakpoints in component styles |
| Prop explosion | Components with many booleans (isPrimary, isSmall, hasIcon, isDanger, noPadding) |
| Override pressure | Many call sites passing className/style overrides to the same shared component |
Count occurrences and note locations.
2. Judge each candidateLink to this section
For each candidate, answer:
| Question | Abstract if… | Don't abstract if… |
|---|---|---|
| How many instances? | 3+ with the same purpose | 1–2, or same look but different purposes |
| Do they change for the same reasons? | A design/behavior change should apply to all | Owned by different features with different evolution |
| Is the behavior hard to get right? | Focus, keyboard, async, a11y, safeguards | Trivial markup |
| Is the variance small and nameable? | Differences fit a few named variants | Every instance is a special case |
| Is the domain stable? | Pattern has been stable for a while | Feature is still being explored |
Verdicts:
- Extract now — clear, stable, repeated, valuable
- Extract primitive only — share the behavior (hook, headless component) but let each feature own presentation
- Consolidate tokens only — values repeat, structure doesn't
- Wait — note it and revisit after more evidence
- Leave duplicated — similarity is coincidental; document why
3. Define boundaries and APILink to this section
For each "extract" verdict:
- Name it by purpose, not appearance (
ConfirmDialog, notRedModal) - Layer it:
- Tokens — color roles, spacing, radii, type, motion, z-layers
- Primitives — behavior without opinionated styling (dialog, popover, listbox, focus scope)
- Components — styled, product-level (Button, Dialog, DataTable, EmptyState)
- Patterns — compositions encoding a product rule (DestructiveConfirm, AsyncButton, ResourceList with states)
- Build behavior in: the component handles its states (loading, disabled with reason, error), focus and keyboard, and safeguards by default — consumers shouldn't be able to forget them
- API shape: variants as an enum (
variant: "primary" | "secondary" | "danger"), not stacked booleans; composition via children/slots for content; controlled/uncontrolled only where needed - Escape hatches exist but are explicit and rare
4. Extract tokens from usageLink to this section
- Collect all raw values by type (colors, spacing, radii, shadows, durations, z-index).
- Cluster near-duplicates (
#1f2937,#1e293b,#20293a→ one role). - Name by role, not value (
color-text-muted,space-4,z-overlay), with a primitive scale underneath if theming is needed. - Map every existing value to a token or flag it as an intentional exception.
- Include behavioral tokens: durations and easings, z-index layers, breakpoints/container sizes, focus ring.
5. Plan adoptionLink to this section
- Build the component with the canonical behavior (from
pattern-consistencyrules where they exist). - Migrate highest-impact call sites first (most used, most defective).
- Codemods for mechanical replacements; manual for behavioral changes.
- Deprecate old versions with a clear marker and a lint rule once migration is feasible.
- Document usage with when to use / when not to use, states, and do/don't examples.
ChecklistLink to this section
- Candidates found with counts and locations
- Every candidate has a verdict with reasoning
- "Leave duplicated" and "wait" decisions are recorded, not silently skipped
- Extracted components named by purpose
- Behavior (states, focus, keyboard, async, safeguards) built into components
- APIs use variants and composition, not boolean piles
- Tokens named by role, mapped from real values, exceptions flagged
- Adoption plan prioritized; deprecation path defined
- Usage docs include when not to use
Common MistakesLink to this section
- Abstracting after two occurrences, then adding a boolean for every new case.
- Merging components that only look alike (a marketing card and a data card) into one with a
typeprop. - Styling-only components — the shared Dialog looks consistent but every feature still implements focus return and Escape differently.
- Tokens named by value (
blue-500-button), which break the moment the brand changes. - Big-bang migration that stalls halfway, leaving two systems.
- No escape hatch, so teams fork the component instead.
- Abstracting unstable features still in product discovery.
ExampleLink to this section
Extraction report (excerpt)
| Candidate | Instances | Evidence | Verdict | Notes |
|---|---|---|---|---|
| Modal/Dialog/Popup | 3 components, 41 usages | Different Escape/outside-click/focus behavior | Extract now | One Dialog with built-in focus return + unsaved guard; see pattern-consistency rule |
| Delete confirmation | 9 hand-rolled | Mix of window.confirm and custom | Extract pattern | DestructiveConfirm requiring objectName, consequence |
| Loading/empty/error wrappers | 23 inline conditionals | Different empty copy, no retry in 14 | Extract pattern | ResourceView rendering the state union from interface-states |
| Settings card vs. pricing card | 2 | Similar look, different purpose & owners | Leave duplicated | Will diverge; marketing owns pricing |
| Status badge | 6 | Same structure, 4 color variants | Extract now | variant: "neutral" | "success" | "warning" | "danger" |
| Spacing values | 212 raw px | 17 distinct values, 6 cover 90% | Consolidate tokens | Map 17 → 8-step scale; flag 3 exceptions |
| Dashboard widgets | 4 | Still being redesigned | Wait | Revisit after Q3 redesign |
Implementation NotesLink to this section
- Headless primitive libraries (Radix, React Aria, Ark, Headless UI, Melt, Reka) can supply primitives so the system focuses on product-level behavior.
- Keep tokens in a format that compiles to CSS custom properties; reference roles in components.
- Visual regression and story coverage for every state of every shared component.
- Measure adoption (imports of new vs. deprecated components) to track migration.
Output ExpectationsLink to this section
Produce:
- Candidate inventory —
Candidate | Instances | Locations | Evidence. - Verdicts — extract now / primitive only / tokens only / wait / leave duplicated, each with reasoning.
- Component specs for extracted items — purpose, layer, API (variants, slots), built-in behavior, states.
- Token map — raw values → role tokens, with exceptions.
- Adoption plan — order, codemods, deprecation, docs.