Skip to content

design-system-evolution

What to abstract from real code, and what to leave alone.

When your agent loads it

Grow a design system out of an existing product's code instead of designing one in the abstract — find duplicate and near-duplicate components, repeated behavioral patterns, and hard-coded values; decide what to abstract, what to leave alone, and when abstraction would hurt; define component boundaries and APIs that encode behavior (states, async, focus, destructive safeguards), extract tokens from real usage, and plan incremental adoption. Produces an extraction report with an abstraction verdict per candidate. Use in mature or fast-grown codebases with copy-pasted components, before building a component library, when a "Button" has 14 props, or when deciding whether to abstract something. Triggers on "duplicate components", "should I abstract this", "component library", "extract design tokens", "refactor components", "design system from existing code". Not for creating a design system from scratch in a design tool.

Group
Product scale
Produces
Extraction report
Length
147 lines
npx skills add aviralj02/interface-skills --skill design-system-evolution

Installs only this skill. Add -g to install globally.

View source

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

  1. Extract from evidence. A pattern that appears three or more times with the same purpose is a candidate. Two instances is often a coincidence.
  2. 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.
  3. Encode behavior, not only style. The most valuable shared components carry the hard parts: states, focus, keyboard, async feedback, safeguards.
  4. Prefer composition over configuration. A component with slots/children scales; one with 30 boolean props does not.
  5. Wrong abstraction costs more than duplication. When in doubt, wait for more evidence.
  6. 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:

SignalHow to find
Duplicate componentsSimilar file names (Modal, Dialog, Popup); similar JSX/template structure; copied files with small diffs
Repeated markup patternsSame element structure + class combinations appearing across features
Repeated behaviorHand-rolled loading/empty/error rendering; custom dropdowns; repeated confirm-delete logic; repeated form field + label + error wrappers
Hard-coded valuesRaw hex/rgb colors, pixel values, z-index numbers, durations, breakpoints in component styles
Prop explosionComponents with many booleans (isPrimary, isSmall, hasIcon, isDanger, noPadding)
Override pressureMany 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:

QuestionAbstract if…Don't abstract if…
How many instances?3+ with the same purpose1–2, or same look but different purposes
Do they change for the same reasons?A design/behavior change should apply to allOwned by different features with different evolution
Is the behavior hard to get right?Focus, keyboard, async, a11y, safeguardsTrivial markup
Is the variance small and nameable?Differences fit a few named variantsEvery instance is a special case
Is the domain stable?Pattern has been stable for a whileFeature 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, not RedModal)
  • 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

  1. Collect all raw values by type (colors, spacing, radii, shadows, durations, z-index).
  2. Cluster near-duplicates (#1f2937, #1e293b, #20293a → one role).
  3. Name by role, not value (color-text-muted, space-4, z-overlay), with a primitive scale underneath if theming is needed.
  4. Map every existing value to a token or flag it as an intentional exception.
  5. 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-consistency rules 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 type prop.
  • 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)

CandidateInstancesEvidenceVerdictNotes
Modal/Dialog/Popup3 components, 41 usagesDifferent Escape/outside-click/focus behaviorExtract nowOne Dialog with built-in focus return + unsaved guard; see pattern-consistency rule
Delete confirmation9 hand-rolledMix of window.confirm and customExtract patternDestructiveConfirm requiring objectName, consequence
Loading/empty/error wrappers23 inline conditionalsDifferent empty copy, no retry in 14Extract patternResourceView rendering the state union from interface-states
Settings card vs. pricing card2Similar look, different purpose & ownersLeave duplicatedWill diverge; marketing owns pricing
Status badge6Same structure, 4 color variantsExtract nowvariant: "neutral" | "success" | "warning" | "danger"
Spacing values212 raw px17 distinct values, 6 cover 90%Consolidate tokensMap 17 → 8-step scale; flag 3 exceptions
Dashboard widgets4Still being redesignedWaitRevisit 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:

  1. Candidate inventoryCandidate | Instances | Locations | Evidence.
  2. Verdicts — extract now / primitive only / tokens only / wait / leave duplicated, each with reasoning.
  3. Component specs for extracted items — purpose, layer, API (variants, slots), built-in behavior, states.
  4. Token map — raw values → role tokens, with exceptions.
  5. Adoption plan — order, codemods, deprecation, docs.