How to avoid the prop explosion, theming chaos, and maintenance nightmares that kill most design systems by year two.
I. The Dream and the Dread
Every team hits the point where they say, "We need a design system."
At first, it feels like liberation: consistent components, faster prototyping, fewer design bugs. But then the cracks start to show.
That shiny Button
component now takes twelve props. Half the team uses Figma variants that don’t exist in code. Changing a color breaks four other features. Adding dark mode feels like rewriting everything.
What started as a unifying force becomes a brittle, bloated, over-abstracted mess.
This article is a survival guide: how to build a design system that won’t collapse under its own weight.
II. The Warning Signs of Collapse
-
Prop Explosion:
<Button variant="outline" size="sm" iconLeft="check" iconRight="x" loading primary secondary destructive subtle />
- Figma vs Storybook vs Reality: No one knows which is the source of truth.
-
Token Fragmentation:
padding-sm
,spacing.small
, andtheme.space.s
all coexist. - Theme Update Hell: Changing one token triggers regressions in 15 components.
"If your design system needs an onboarding course, it’s probably too complicated."
III. Design System Survival Rules
1. Start Token-First, Not Component-First
Before you build a single component, define your design tokens:
- Color
- Spacing
- Typography
- Elevation
- Border radii
Centralizing these makes theming easier and faster. Use tools like Style Dictionary or alternatives like Theo, Token Studio, or Design Tokens CLI or a plain JSON/YAML token source. Sync them with Figma if possible.
Here’s a basic example of a token structure, including both base and semantic tokens:
{
"color": {
"base": {
"blue500": { "value": "#0055ff" },
"gray100": { "value": "#f5f5f5" }
},
"semantic": {
"primary": { "value": "{color.base.blue500}" },
"background": { "value": "{color.base.gray100}" },
"buttonBackground": { "value": "{color.semantic.primary}" }
}
},
"spacing": {
"s": { "value": "8px" },
"m": { "value": "16px" }
}
}
Style Dictionary compiles this into platform-specific formats (CSS variables, SCSS, JS, etc.), enabling semantic tokens to be updated globally without refactoring all usage instances.
2. Favor Composition Over Configuration
Avoid the mega-component trap. Instead of making a <Card>
that supports 20 props, use slots and child components. Here's a comparison:
Prop Explosion:
<Card heading="Welcome" content="This is a composed card." footerAction={<Button>Submit</Button>} />
Composed Pattern:
<Card>
<CardHeader>
<Heading level={3}>Welcome</Heading>
</CardHeader>
<CardContent>
<Text>This is a card component built with composition.</Text>
</CardContent>
<CardFooter>
<Button variant="primary">Submit</Button>
</CardFooter>
</Card>
Composition improves clarity, flexibility, and long-term maintainability.
3. Draw the Line Between Design System and App Layer
Your design system shouldn’t know your app’s business logic.
Bad:
<Button isUserAdmin={user.role === 'admin'} />
Good:
{user.role === 'admin' && <Button>Delete</Button>}
Keep components pure and visual, with composable APIs.
4. Test Like It’s a Product (and Perform Under Pressure)
Your design system is a product. Treat it like one.
- Use visual regression tests (e.g., Chromatic, Percy, or Loki)
- Add accessibility checks in CI (e.g., axe-core, Lighthouse)
- Require design QA signoff on Storybook previews
- Test performance of shared components under real-world loads:
- Rendering 1,000+ rows in a table
- Resizing responsive grid layouts
- Reflowing content-heavy pages with mixed media
Establish performance baselines:
- Initial render under 200ms
- Interaction latency under 100ms
- Cumulative Layout Shift (CLS) below 0.1
Use tools like Lighthouse, WebPageTest, or RUM-based metrics to monitor these thresholds in production.
You’re not just testing code—you’re testing expectations. Accessibility isn’t just about contrast ratios—verify focus states, keyboard navigation, and screen reader labels. Design systems used globally should also plan for internationalization: RTL layouts, dynamic content lengths, and translated UI states.
5. Prune Aggressively
Design systems rot when you keep everything forever.
- Deprecate unused props and variants
- Maintain a component health score (usage, last updated, bug count)
- Review usage quarterly. If no one uses
Tag.variant="holographicRainbow"
, kill it
6. Assign Ownership, Governance, and Versioning
A design system needs clear ownership. Establish a small core team—designers, developers, and possibly a product lead—who review proposals, maintain standards, and resolve conflicts. Without dedicated stewards, entropy wins.
One effective way to structure decision-making is through a lightweight Design System RFC (Request for Comments) process:
- Anyone proposing a change writes a short Markdown file detailing the rationale, implementation sketch, visual impact, and migration plan.
- The core team reviews RFCs weekly or bi-weekly and approves, rejects, or requests changes.
- Approved changes are logged in a changelog and communicated via Slack, Confluence, or a design system newsletter.
This process provides visibility, structure, and a consistent cadence for evolution—without bottlenecking progress.
Here’s an example of what an RFC might look like:
# RFC: Add new `Tooltip` component
## Summary
Create a `Tooltip` primitive for accessibility-compliant hover and focus hints.
## Design Rationale
Tooltips are currently inconsistent and not accessible across teams. This component will wrap Popper.js and ensure compliance.
## Proposed API
<Tooltip content="Helpful info">
<Button>Hover me</Button>
</Tooltip>
## Migration Strategy
No migration needed. Optional enhancement.
## Review Timeline
Open for feedback until next Friday.
Each RFC lives in a version-controlled folder (e.g., /design-system/rfcs
) and gets reviewed weekly.
To reduce disruption when making breaking changes, use semantic versioning and changelogs. Prefix major changes with v2.0.0
, and document migration paths using a dedicated MIGRATIONS.md
. For widely-used components or tokens, consider releasing codemods, migration guides with visual diffs, and setting deprecation warnings in CI or console logs to smooth the transition.
7. Encourage Adoption by Design
The best system is the one people actually use. Provide clear documentation, low-friction onboarding, and support channels. Highlight wins: “this page shipped in half the time because we used the system.” Adoption is a culture, not just a mandate.
IV. Migration in Practice: A Real-World Transition
At one mid-sized SaaS company, the original design system had ballooned to over 130 components, many of them single-use. Tokens were duplicated across files, and Figma often didn’t match what was in production.
To transition, the team:
- Conducted a component usage audit to identify high-value, redundant, and legacy elements
- Defined a token source of truth and synced it with Figma and code using Tokens Studio and Style Dictionary
- Introduced a “new system only” rule: all new features had to use the rebuilt library
- Applied composition patterns to reduce prop explosion (especially in layout and card components)
- Used codemods and gradual migration flags to port old pages incrementally
Common pitfalls to avoid:
- Failing to clearly deprecate old components, leading to dual systems
- Not aligning design and engineering timelines
- Migrating without a performance baseline, making regressions hard to detect
Over three months, they reduced the component count by 40%, shrunk the bundle by 22%, and were able to roll out dark mode with just three lines of token overrides.
V. Bonus: Figma Drift and How to Fix It
Even a solid system can fail if design and code diverge.
Strategies:
- Use Figma token plugins (like Tokens Studio) to sync code and design
- Limit who can create new Figma variants
- Hold "design system syncs" every 4–6 weeks across design/dev
- Align Figma variants with coded component states (loading, focus, error)
- Periodically audit Figma vs. Storybook parity
This doesn’t require a full-time bridge team—but it does require shared discipline.
Remember: your source of truth isn’t code or Figma. It’s the relationship between them.
VI. The Payoff
A lightweight, well-maintained design system pays off every time someone starts a new page or feature:
- No pixel-pushing
- No guesswork
- No rewrite every quarter
"The best design systems aren’t complex. They’re clear."
Build yours to last.
Top comments (0)