Skip to main content

Actively Looking

Building Design Systems at Scale

Lessons from architecting component libraries for teams of 50+ engineers across multiple products and platforms.

Published
Reading Time5 min read
CategoriesDesign Systems, Engineering
AuthorDavid Dias

Design systems are never just component libraries. They are the shared language between design, engineering, product, and the people who have to ship under pressure.

After working on component systems for large teams, I learned something the hard way: the technical part is usually manageable. The real work is helping people make consistent decisions when the product is moving fast.

The Foundation: Start with Principles

Before writing a single line of code, decide what the system should protect. Not in a vague "quality" sense. I mean the practical rules that help a team choose one path over another when two options both look reasonable.

Our core principles:

  • Accessibility First: WCAG 2.1 AA compliance isn't optional
  • Composability Over Configuration: Prefer small, focused components
  • Progressive Enhancement: Work without JavaScript, better with it
  • Performance Budget: Every component must meet Core Web Vitals

These principles made technical decisions easier. API design, bundle size, accessibility, naming, documentation. They all had something to answer to.

Architecture Decisions That Matter

Component API Design

The hardest part is not building the component. The hard part is designing an API that supports real use cases without turning into a drawer full of props.

// ❌ Bad: Too many props, unclear purpose
<Button
  variant="primary"
  size="medium"
  loading={false}
  disabled={false}
  icon="left"
  iconName="arrow"
  fullWidth={false}
  // ... 15 more props
>
  Click me
</Button>
 
// ✅ Good: Composable, clear intent
<Button variant="primary" size="medium">
  <Icon name="arrow" position="left" />
  Click me
</Button>

This is where I prefer composition. Smaller pieces with clear jobs are easier to understand, easier to test, and much easier to delete later.

Type Safety Without Overhead

TypeScript helps a lot here, but it can also become theatre. I want types that catch real mistakes without making every consumer fight the API.

// Component props with proper inference
type ButtonProps<T extends ElementType = 'button'> = {
  as?: T
  variant?: 'primary' | 'secondary' | 'ghost'
  size?: 'small' | 'medium' | 'large'
} & ComponentPropsWithoutRef<T>
 
// Usage: Fully type-safe, no manual type assertions needed
<Button as="a" href="/home">Link Button</Button>
<Button onClick={handleClick}>Regular Button</Button>

The as prop pattern gave us flexibility without making people reach for manual type assertions. That was the balance I wanted.

Documentation as a Product

People do not adopt a design system because it exists. They adopt it because it saves them time at the exact moment they are stuck.

The documentation that actually helped had a few things in common:

  1. Live examples: Every component had interactive examples
  2. Do's and Don'ts: Visual examples of correct usage
  3. Accessibility notes: Keyboard navigation, ARIA patterns, screen reader behavior
  4. Code snippets: Copy-paste ready examples
  5. Migration guides: Clear paths from old to new patterns

This is the same reason I built UX Patterns. Developers do not just need a rule. They need the reason behind it, the edge cases, and a practical example they can use without opening five tabs.

The Hidden Challenge: Governance

The best design system in the world fails if teams do not use it consistently. Governance sounds heavy, but good governance is not control. It is support.

The parts that helped most were boring in the best way:

  • Office hours: Weekly sessions for questions and pair programming
  • Component champions: One person per team responsible for adoption
  • Automated checks: Linting rules for accessibility and best practices
  • Contribution guidelines: Clear process for proposing new components
  • Regular audits: Quarterly reviews of component usage and pain points

Performance at Scale

When 50+ engineers ship code every day, performance problems do not arrive all at once. They creep in through small decisions.

The safeguards had to be visible and repeatable:

  • Bundle size budgets: Each component has a maximum size
  • Tree-shaking: Only import what you use
  • Code splitting: Async loading for heavy components
  • Zero-runtime CSS: Use CSS-in-JS only when necessary

I think of this as release hygiene. A checklist does not replace judgment, but it catches the things people miss when they are tired. That is the spirit behind the Front-End Checklist.

// Lazy load heavy components
const DataTable = lazy(() => import('@design-system/data-table'))
 
function MyPage() {
  return (
    <Suspense fallback={<Skeleton />}>
      <DataTable data={data} />
    </Suspense>
  )
}

We monitored bundle impact in CI and failed builds that went over budget. Annoying sometimes, useful almost always.

Versioning and Breaking Changes

Semver matters, but the version number is only part of the experience. A breaking change still has to respect the person doing the upgrade.

  • Deprecation warnings: Console warnings before removal
  • Codemods: Automated migration scripts
  • Breaking change policy: Major versions only once per quarter
  • LTS versions: Support previous major for 6 months

This reduced upgrade friction. More importantly, it made the system feel predictable.

What I'd Do Differently

Looking back, I would change a few things:

  1. Start smaller: We built too many components upfront. I would focus on high-value patterns first.
  2. Measure adoption: Track usage from day one so the system can react to reality.
  3. Invest in tooling earlier: Better dev tools make adoption easier. Projects like LLMs.txt Hub also changed how I think about documentation that machines can read, not only humans.
  4. Document design decisions: Capture why choices were made, not just what was shipped.

Key Takeaways

Building a design system that scales requires:

  • Clear principles that guide decisions
  • APIs that balance flexibility and simplicity
  • Documentation that treats users as customers
  • Governance that enables rather than controls
  • Performance monitoring and enforcement
  • A versioning strategy that respects users' time

The best design systems fade into the background. Teams move faster, products feel more consistent, and users get a better experience without ever knowing the system exists. That is the goal.


Want to discuss design system architecture? Reach out. I'm always happy to talk shop.

Share This Article