Design systems that scale: the case for composability

Many design systems eventually hit the same wall: By growing to accommodate every variation teams need, they become gruelling to maintain.

A designer needs a button with an icon on the right, not the left, for example. Or an engineer needs a modal that can nest inside another modal. And so on.

Soon enough, the system bends under the weight of prop after prop until it becomes harder to use than to work around. We see this pattern repeatedly on large-scale UI systems.

There is a solution: composability — a principle by which design systems continue to work well as they scale.

Composability should be a core principle when building UI, essential both for large systems and smaller systems with big ambition. But it’s poorly understood, and therefore often overlooked.

Here’s how to apply composability as the foundation of a successful, large-scale design system.

Organisation by technology — the problem composability solves

In early web development, the dominant approach was document-first. HTML, CSS and JavaScript were separated into distinct layers, each with its own concerns: structure, style and behaviour respectively. MarkoJS call this separation of concerns by technology.

Many people still passionately believe in separation by technology, but this purity is unhelpful in modern contexts. Not least because both CSS and HTML have gained behavioural capabilities themselves.

At scale, with large teams working on different parts of complex applications, separation by technology struggles. A CSS change to fix one page mysteriously breaks another. A JavaScript tweak cascades through unrelated features.

The separation that should contain the complexity instead disperses it. When systems become overly dependent on each other across these boundaries, it's known as high coupling — the classic anti-pattern that keeps on giving.

The emergence of composability, or organisation by functionality

Ironically, composability predates the web. It emerged as a principle in 1970s computer science and mid-20th century systems theory.1 It involves building complex systems from smaller, self-contained parts with clear boundaries. It’s separation of concerns by functionality rather than technology.

Composability in this context means defining what a component does, how it relates to other components, and crucially, how it protects itself from unintended interactions. When done properly, parts can be reused, recombined, and updated without unintended side effects rippling through the system.

Modern JavaScript frameworks, particularly React, formalised this approach through component models. Each component keeps its own implementation close by, while staying small enough to be composed from and alongside others.

Why it’s powerful

For large organisations, composability isn't just a nice-to-have. It's the difference between a system that enables teams and one that obstructs them.

Composability reduces the need for cross-team coordination. Teams can work independently within well-defined boundaries, making parallel work possible without treading on each other’s toes.

Instead of negotiating every change, teams can simply get on with their bit. No more @here distraction bombs dropped into Slack.

It also makes updates safer and easier to test. Changes are contained, reducing the blast radius of bugs.

Conversely, when composability is overlooked, teams tend to work around the system. As a result, bad things happen:

Designers detach components from the system and retreat into Figma, widening the gap between design and implementation2

Engineers fork components or rebuild from scratch, fragmenting the codebase

Component APIs accumulate baroque collections of props (showIconLeft, showIconRight, iconLeftSize, iconRightSize, iconLeftColor…)

Styles and behaviours become context-dependent and unpredictable

A composable system prevents these outcomes. Components can be assembled with confidence, and behaviour remains consistent regardless of context.

The composability–configuration continuum

Note: This section contains contrived examples for purely illustrative purposes.

Composability and configuration aren't mutually exclusive. They represent different points on a spectrum, each with distinct trade-offs.

At the configuration end, teams extend functionality through props and toggles. A typical configuration-based button might look like:

<Button
icon="arrow"
iconPosition="right"
size="large"
variant="primary"
/>

It's intuitive and requires minimal learning. But configuration doesn't scale gracefully. Each new requirement adds props. Each prop increases testing and maintenance overhead. Performance degrades. The API becomes harder to reason about.

At the composability end, systems provide smaller, general-purpose building blocks that teams combine. Instead of a Button component with an icon prop, you might have:


<ButtonText>
Continue
</ButtonText>
<ButtonIcon>
<Arrow />
</ButtonIcon>


The trade-off is that it requires more discipline up front. Teams must understand how components fit together.

But it scales better: new requirements are handled through new combinations of existing parts rather than adding new props. The component stays simple and the API stays stable.

Most systems land somewhere between these extremes. But composability serves as a useful north star. A well-designed composable system makes the right thing easy and the wrong thing difficult — not through restriction, but through sensible defaults and clear APIs.

Designing composable systems

Designing for composability demands intention.

Key principles include:

Co-locate structure, style and logic: Everything related to a component should live together.

Support internal composition: Design components that accept children, use slot patterns, or provide sub-components that work together.

Create clear, documented prop APIs: Every prop should have clear purpose, naming and type constraints. Avoid generic escape hatches like className or style props that allow arbitrary overrides.

Use explicit prop types and validation: TypeScript interfaces or PropTypes that make correct usage obvious and incorrect usage difficult.

Choose sensible defaults: Defaults that encourage correct usage without requiring configuration.

Composability is less about building a perfect system and more about designing for change. It allows systems to evolve safely. Versioning makes it possible to release early, gather data from real-world use, and iteratively improve.

Learning from existing systems

Existing design systems can provide useful reference points. It’s often said that teams need to build a bad design system before they can build a good one. Reviewing what’s already out there can accelerate that learning process.

There are many well-documented public design systems available. Building small features with them is a good way to see how composable patterns behave, and how boundaries are defined and respected in actual use.

Radix UI provides unstyled, accessible components built on composition. Material UI, Ant Design and Carbon similarly show different approaches to balancing composition and configuration. Primer demonstrates how composability works in a large, multi-platform system.

Adopting composability

It’s possible to shift from a configuration-heavy system to a more composable one, but it’s rarely straightforward. You can't simply deprecate the old and ship the new.

You often end up with two systems living side by side: one designed for configuration, the other for composition. This creates confusion and duplication. You carry the baggage of the old system for a long time — maybe forever.

In cases where existing systems must remain in place — what we call heritage systems — pragmatic workarounds may be necessary. But where new systems can be introduced, composability should be the starting point.

Strategies that help:

Version new components separately: Make it clear which components represent the new approach, reducing confusion about which to use.

Document thoroughly: Don't assume teams will figure things out. Provide concrete examples of translating old patterns to new ones.

Accept coexistence of systems: Fighting the reality burns goodwill and slows adoption.

Start small

If composability is new territory for your team, don’t rush into a big commitment.

Here are things you can do in the early stages:

Audit a problematic component: This might be a button, card, or modal with dozens of props. Map which props are actually used, which conflict, and which are workarounds. This reveals where configuration has failed.

Sketch a composable alternative: How would you rebuild it using composition? You don't need to implement it. Just design the API. What becomes simpler? What becomes harder?

Build a pilot component: Choose something new rather than a replacement for existing work. A feature that needs a component the system doesn't provide yet. Build it composably from the start.

Review an established system: Pick an established public design system. Build a small feature with it. Notice what feels natural, what feels awkward, how the pieces fit together.

Share your learning: Document what you've learned. Show examples. Build shared understanding before attempting larger changes.

Worth the effort

Composability is not a new idea. It’s a practical principle that supports maintainability, development speed, and consistency — particularly in complex environments.

It requires clarity of intent, thoughtful design, and a willingness to change how teams work. But the investment is worth it to build more resilient systems that you can iterate faster, and which your teams can trust without working around.

Footnotes

Inessential reading rabbit hole: Ludwig von Bertalanffy’s General system theory: foundations, development, applications (1968), David Parnas’s On the criteria to be used in decomposing systems into modules (1972), John Backus’s Function level programs as mathematical objects (1981). ↩

In Don’t Detach: Why Figma Component Health Matters, Salesforce’s Lightning Design System team identifies detaching as a critical threat to design system health. ↩