Building Design Systems at Scale: Lessons from the Trenches
How we built a design system that serves 12 product teams across 3 time zones — and the hard-won lessons that made it work.
Why Most Design Systems Fail
Here's an uncomfortable truth: most design systems die. Not with a dramatic failure, but with a slow fade into irrelevance as teams quietly stop using them and build their own components instead.
After building (and, honestly, failing at) design systems at three different companies, we've identified the pattern that separates living systems from abandoned ones.
A design system is not a component library. It's a shared language between design and engineering, and like any language, it dies if nobody speaks it.
The Foundation: Principles Over Pixels
Before writing a single line of CSS, we established five non-negotiable principles:
- Adoption is optional — If teams choose our system, it's because it's genuinely better than the alternative
- Composition over configuration — Small, focused primitives that compose into complex patterns
- Accessible by default — WCAG 2.1 AA compliance is not a feature, it's a baseline
- Performance budget — Every component has a bundle size budget, enforced in CI
- Documentation is the product — If it's not documented, it doesn't exist
Tip
The single most impactful decision we made was making adoption optional. It forced us to build something genuinely useful rather than mandating usage through organisational authority.
Architecture Decisions
Token-First Design
Everything starts with design tokens. Colours, spacing, typography, shadows — all defined as tokens that flow from Figma to code through an automated pipeline.
{
"color": {
"primary": {
"50": { "value": "#eef6ff", "type": "color" },
"500": { "value": "#2f7bff", "type": "color" },
"700": { "value": "#0d44e1", "type": "color" },
"900": { "value": "#14338f", "type": "color" }
},
"semantic": {
"success": { "value": "{color.secondary.500}", "type": "color" },
"warning": { "value": "#f59e0b", "type": "color" },
"error": { "value": "#ef4444", "type": "color" }
}
},
"spacing": {
"xs": { "value": "4px", "type": "spacing" },
"sm": { "value": "8px", "type": "spacing" },
"md": { "value": "16px", "type": "spacing" },
"lg": { "value": "24px", "type": "spacing" },
"xl": { "value": "32px", "type": "spacing" }
}
}
Component API Design
We follow a strict API design philosophy for every component:
<script setup lang="ts">
// Every component follows this pattern:
// 1. Props with sensible defaults
// 2. Named slots for composition
// 3. Emits for parent communication
// 4. Expose for imperative access (rare)
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
}
withDefaults(defineProps<ButtonProps>(), {
variant: 'primary',
size: 'md',
disabled: false,
loading: false,
});
defineEmits<{
click: [event: MouseEvent];
}>();
</script>
Info
We enforce a rule: no component may have more than 8 props. If you need more, you're building a page, not a component. Decompose further.
The Hardest Part: Governance Without Bureaucracy
Design systems need governance to stay coherent, but too much process kills velocity. We landed on a model we call "Propose, Prove, Promote":
Propose
Any engineer or designer can propose a new component or pattern. They open an RFC (Request for Comments) — a lightweight document answering three questions:
- What problem does this solve?
- Who else has this problem?
- What alternatives did you consider?
Prove
The proposer builds a working prototype. Not a full production component — just enough to validate the approach. This runs for 2-4 weeks in a real product.
Promote
If the prototype proves valuable, it enters the design system backlog. A rotating "Systems Guild" of 4 engineers (one from each product area) reviews and builds the production version.
Our contribution rate went from 2 components per quarter to 8 after introducing this model. More importantly, rejection rate dropped to near zero because components are battle-tested before they enter the system.
Measuring Success
Adoption metrics tell you if teams are using the system. Quality metrics tell you if they should be.
What We Track
- Component coverage: What percentage of UI elements in production come from the design system? (Target: >80%)
- Time to first component: How long does it take a new engineer to use their first design system component? (Target: <1 hour)
- Contribution velocity: How many components are contributed per quarter?
- Bug escape rate: How many component bugs make it to production? (Target: <2 per quarter)
- Bundle impact: Total JS/CSS added by design system components per page
Current Numbers
After 18 months:
- 87% component coverage across all products
- 23 minutes average time to first component for new hires
- 8-10 components contributed per quarter
- 1.2 bugs escaped to production last quarter
- 34KB average bundle impact per page (gzipped)
Lessons Learned the Hard Way
1. Don't Abstract Too Early
We initially built a highly configurable DataTable component with 23 props. Nobody used it because it was harder to understand than writing a custom table. We replaced it with three focused components: SimpleTable, SortableTable, and PaginatedTable. Adoption tripled overnight.
2. Documentation Must Include "Why Not"
For every component, we document when not to use it. This builds trust. Teams know we're not trying to force everything through the design system.
3. Invest in Dev Tooling
Our Storybook instance has:
- Live Figma embeds next to every component
- Accessibility audit results per story
- Bundle size badges
- Copy-paste-ready code snippets with real data
This investment paid for itself within the first month.
4. Dark Mode from Day One
Retrofitting dark mode into an existing design system is a nightmare. We learned this the hard way at a previous company. At CNEX, dark mode support was a day-one requirement for every component. It's enforced in our CI pipeline — if a component doesn't pass visual regression in both themes, it doesn't ship.
Warning
If you're building a design system today and not supporting dark mode from the start, you're creating technical debt that will be extremely expensive to pay off later.
What's Next for Our System
We're currently working on three major initiatives:
- AI-assisted component generation — Using our design tokens and component patterns to generate new component variants automatically
- Cross-platform tokens — Extending our token pipeline to iOS (SwiftUI) and Android (Compose)
- Usage analytics dashboard — Real-time visibility into which components are used where, helping us prioritise maintenance
The design system is never "done." It evolves with the products it serves, the teams that use it, and the platforms it targets. That's what makes it a living system rather than a static library.
Building a design system for your organisation? Let's talk about how we can help.
Written by
CNEX Team
Building the next generation of enterprise software at CNEX. Passionate about AI, cloud-native architecture, and elegant solutions to complex problems.
Related articles
The API-First Approach: Why Leading Companies Build APIs Before Interfaces
API-first development is reshaping how companies build software. Learn the principles, benefits, and implementation patterns behind this modern approach.
Inside Connekz: How We Built an AI Agent That Books, Sells, and Supports
A behind-the-scenes look at building Connekz — the AI agent platform powering customer interactions for businesses across New Zealand and beyond.
Cloud-Native Architecture: Building for Global Scale
A deep dive into the architectural patterns and practices that enable enterprise applications to scale globally while maintaining reliability and performance.
