Product Design

How to govern a design system across 10+ consumer projects

A practical playbook for governing a design system across many consumer projects: contribution model, versioning, codemods, and adoption tracking.

April 27, 20268 min read
How to govern a design system across 10+ consumer projects

You ship a design system. One project consumes it. Easy. Five projects consume it. Manageable. Ten or more consume it, each on a different version, with their own deadlines and their own engineers who file issues at midnight, and the system stops scaling. Tokens drift. Breaking changes block release trains. Nobody knows who owns the Button component this quarter.

This guide walks through the seven moves that keep a design system useful when ten or more downstream projects depend on it. The result is a governance model that lets you ship quickly without breaking consumers, with one core team that stays small, and adoption metrics you can actually defend in a quarterly review.

The shape of the problem

A design system that serves one project is a component library. A design system that serves ten projects is an internal platform. The work is no longer about adding components. It is about coordinating change across a fleet you do not own, where every consumer has its own roadmap, its own React version, and its own tolerance for migrations.

Three failure modes recur. First, consumers fork the system to unblock themselves and never come back. Second, the core team blocks every change in code review, becoming a bottleneck. Third, breaking changes ship without a migration path, and consumers stay on a version that is two years stale because upgrading costs a sprint.

The seven steps below address each failure mode directly.

Step 1: Pick a contribution model and write it down

Three models exist. Each fits a different team shape.

Centralized. A single dedicated team owns everything. Strong consistency, slow scaling. As Nathan Curtis put it in the canonical EightShapes piece, overlords don't scale. Use this only when the system is young and the consumer count is below five.

Federated. Many teams contribute. No single owner. Fast in the short term, expensive in the long term: components diverge, decisions drag, and consistency drops. Pure federation is rare and we don't recommend it.

Hybrid (cyclical). A small core team owns the API, the tokens, and the release pipeline. Embedded contributors from product teams ship components and propose changes. Salesforce describes this as a cyclical model: centralized governance plus federated contribution, each informing the other.

For 10 or more consumers, hybrid is almost always the right answer. Document the model in a CONTRIBUTING.md at the root of the repo: who can merge to main, who can propose components, who decides on tokens, what the SLA on PR review is. Without that document, every escalation becomes a meeting.

Step 2: Freeze the public API

The single largest source of accidental breakage in a design system is exporting things you did not mean to export. A consumer imports a private utility, you refactor it six months later, and their build breaks at 2 a.m.

Define a public API surface explicitly. One barrel file (an index.ts at the package root) exports the components, tokens, hooks, and types that consumers can use. Everything else is internal. Configure the bundler so that internal modules are not addressable through deep imports, or add an ESLint rule (no-restricted-imports) that bans deep imports in consumer projects.

This single move turns 80% of accidental breakage into intentional breakage you can plan for.

Step 3: Version with SemVer, ship a changelog every release

Use Semantic Versioning at the package level. MAJOR bumps signal breaking API changes. MINOR bumps add backward-compatible features. PATCH bumps fix bugs without changing behavior. Atlassian and other large publishers also version each component independently, which gives consumers finer-grained release notes; for a small core team, package-level versioning is enough and easier to communicate.

Every release ships a changelog entry that names: what changed, who is affected, and (for MAJOR releases) the migration path. Tools like Changesets or release-please automate this from PR descriptions, so contributors write the changelog as they merge, not after the fact.

The discipline that matters: a MAJOR bump is never silent. Consumers learn about it from the changelog or from a Slack message, never from a broken CI build.

Step 4: Ship codemods with every breaking change

A breaking change without a migration path is a tax on every consumer. With ten consumers, that tax compounds: ten engineers, ten contexts to load, ten chances for a typo.

The fix is jscodeshift or a similar AST-based transform tool. When you rename a prop, kill a component, or change an import path, you write a codemod and ship it in the same release as the breaking change. MUI and Chakra UI both ship codemods with major versions, and consumers run a single command to migrate.

Two practical rules:

  • Test the codemod against the largest consumer first. Codemods that break on edge cases are worse than no codemod, because consumers stop trusting them.
  • Bundle the codemods in a CLI under your design system package, so the upgrade path is one documented command, not a hunt through GitHub.

The Hypermod team has documented the codemod-driven design system pattern in detail; their core argument is that automated migration changes the economics of breaking changes from "we cannot afford to break" to "we can refactor freely as long as the codemod follows".

Step 5: Track adoption with a manifest per consumer

You cannot govern what you cannot measure. Each consumer project should publish a ds.manifest.json at its root, committed to git, that records:

  • The current design system version it depends on.
  • The number of design system components actually used (counted from imports).
  • The number of overrides (custom CSS that bypasses the system).
  • The last manual audit date.

A nightly job aggregates these manifests across the fleet and produces one dashboard. The numbers that matter are coverage (percentage of UI files importing the design system), drift (count of CSS overrides per project), and version distance (how many MAJOR releases behind each consumer is).

Brevo, Productboard, and Mews have all published their version of this approach. Brevo's adoption tracker and Productboard's coverage report are good public references. The shared insight: adoption metrics derived from production code beat survey-based metrics every quarter.

Step 6: Set deprecation timelines and enforce them

A deprecated component that lives forever is not deprecated, it is supported. To deprecate cleanly:

  1. Mark the component as deprecated in the source (a JSDoc @deprecated tag, a console warning in development).
  2. Announce a removal date in the changelog. We use 6 months from announcement for low-traffic components, 12 months for high-traffic ones.
  3. Track which consumers still import the deprecated component using the manifest from Step 5.
  4. Send targeted reminders at 90 days, 30 days, 7 days before removal.
  5. Remove on the announced date, in a MAJOR release, with a codemod.

The hardest part is enforcement. Teams ask for extensions, and granting extensions teaches everyone that deadlines are negotiable. Don't.

Step 7: Run a fixed bump cadence

Without a cadence, consumers upgrade at random, and the spread of versions across the fleet grows unbounded. A fortnight or a month works for most teams. The cadence has three artifacts:

  • A scheduled release on a fixed day (we use the first Tuesday of the month).
  • A pre-flight check the week before, scanning consumer manifests for risk: which projects have the most overrides, the largest version distance, or the heaviest deprecated-component usage.
  • A retro after each release, reviewing what broke and why.

Predictability is the goal. Consumers can plan a half-day per cycle for the upgrade. Engineers do not get paged for surprise migrations.

What you trade off

This setup is not free. The core team spends real time on changelogs, codemods, deprecation tracking, and the manifest pipeline. For a system serving fewer than five consumers, the overhead is hard to justify and a lighter process wins. Past ten consumers, the overhead pays for itself within two release cycles, because the alternative is reactive firefighting that scales worse than a small platform team.

The other trade-off is autonomy. A hybrid model with a frozen public API means contributors cannot ship anything they want overnight. They propose, the core team reviews, the change lands in the next release. This is slower than letting every team patch the system in place. It is also the only way to keep ten consumers on the same version of Button.

For a deeper look at the kinds of mistakes this governance model prevents, see our piece on design system mistakes that wreck consistency at scale.

Sources

Photo by Ilya Semenov on Unsplash

Studio

Start a project.

One partner for companies, public sector, startups and SaaS. Faster delivery, modern tech, lower costs. One team, one invoice.