Back to Design Systems
Topic 8 of 8

Architecture & Scaling

Mono-repos, versioning, distribution, and scaling design systems across teams

Scaling Design Systems

As your design system grows beyond a single team, you need solid architecture for versioning, distribution, and multi-team collaboration. A well-architected system can serve hundreds of developers across dozens of projects.

🏗️ Architecture Decisions

Monorepo

All packages in one repo (Turborepo, Nx)

Multi-repo

Separate repos per package

Single Package

Everything bundled together

Federated

Distributed ownership across teams

Monorepo Setup with Turborepo

# Create a new Turborepo
npx create-turbo@latest my-design-system

# Folder structure
my-design-system/
├── apps/
│   ├── docs/              # Storybook or documentation site
│   └── playground/        # Test/demo app
├── packages/
│   ├── tokens/           # Design tokens
│   ├── ui/               # React components
│   ├── utils/            # Shared utilities
│   └── config/           # Shared configs (ESLint, TypeScript)
├── turbo.json
├── package.json
└── pnpm-workspace.yaml

# turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {},
    "test": {
      "dependsOn": ["build"]
    }
  }
}

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

# Run all builds
pnpm turbo run build

# Run only changed packages
pnpm turbo run build --filter=...[HEAD~1]

📖 Turborepo Documentation →

Package Structure

// packages/ui/package.json
{
  "name": "@acme/ui",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    },
    "./button": {
      "import": "./dist/button.mjs",
      "require": "./dist/button.js",
      "types": "./dist/button.d.ts"
    }
  },
  "sideEffects": ["**/*.css"],
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@acme/tokens": "workspace:*",
    "tsup": "^7.0.0",
    "typescript": "^5.0.0"
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
  }
}

// packages/ui/src/index.ts
export * from './Button';
export * from './Card';
export * from './Input';
// ...

// packages/ui/tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts', 'src/button.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  splitting: true,
  clean: true,
  treeshake: true,
  external: ['react', 'react-dom'],
});

Versioning with Changesets

# Install changesets
pnpm add -Dw @changesets/cli

# Initialize
pnpm changeset init

# When you make a change, add a changeset
pnpm changeset
# This prompts: What packages changed? Major/minor/patch? Description?

# Creates a file like:
# .changeset/brave-eagles-dance.md
---
"@acme/ui": minor
"@acme/tokens": patch
---

Added new Avatar component with image fallback support.

# Versioning workflow
pnpm changeset version   # Bumps versions, updates CHANGELOG
pnpm changeset publish   # Publishes to npm

# In CI (GitHub Actions)
- name: Create Release PR or Publish
  uses: changesets/action@v1
  with:
    publish: pnpm changeset publish
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

📖 Changesets Documentation →

Publishing to npm

# .npmrc for private registries
@acme:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

# Or use npm's public registry
# Ensure package.json has:
{
  "name": "@acme/ui",
  "publishConfig": {
    "access": "public"
  }
}

# Build and publish script
# package.json (root)
{
  "scripts": {
    "release": "pnpm build && pnpm changeset publish"
  }
}

# GitHub Actions workflow
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'
          registry-url: 'https://registry.npmjs.org'
      
      - run: pnpm install
      - run: pnpm build
      
      - name: Publish
        uses: changesets/action@v1
        with:
          publish: pnpm changeset publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Multi-Team Contribution

// CODEOWNERS file for GitHub
# .github/CODEOWNERS

# Core team owns everything by default
* @acme/design-system-core

# Specific component ownership
/packages/ui/src/data-table/ @acme/data-team
/packages/ui/src/charts/ @acme/analytics-team
/packages/tokens/ @acme/design-team

# Contribution Guidelines (CONTRIBUTING.md)
## Adding a New Component

1. Create a proposal issue using the template
2. Get approval from design-system-core team
3. Create component following patterns in /packages/ui/src/Button
4. Add Storybook stories
5. Add unit tests
6. Add changeset
7. Open PR and request review

## Component Checklist
- [ ] TypeScript types exported
- [ ] Storybook story with all variants
- [ ] Unit tests with >80% coverage
- [ ] Accessibility tested (keyboard + screen reader)
- [ ] Documentation in Storybook MDX
- [ ] Changeset added

Consuming Design Systems

# Installing the design system
npm install @acme/ui @acme/tokens

# In your app
import { Button, Card } from '@acme/ui';
import '@acme/ui/styles.css'; // Or import tokens

// Tree-shaking: only imports what you use
import { Button } from '@acme/ui/button';

// Version pinning strategies

// package.json
{
  "dependencies": {
    // Exact version (safest, but manual updates)
    "@acme/ui": "2.1.0",
    
    // Caret: allows minor updates (recommended)
    "@acme/ui": "^2.1.0",
    
    // Tilde: allows patch updates only
    "@acme/ui": "~2.1.0"
  }
}

// Renovate config for auto-updates
// renovate.json
{
  "extends": ["config:base"],
  "packageRules": [
    {
      "matchPackagePatterns": ["@acme/*"],
      "automerge": true,
      "automergeType": "branch"
    }
  ]
}

Design System Tooling

Turborepo

High-performance monorepo build system

Nx

Full-featured monorepo toolkit with generators

tsup

Zero-config TypeScript bundler powered by esbuild

Changesets

Versioning and changelog management for monorepos

Chromatic

Visual testing and review for UI components

💡 Scaling Tips

  • • Start small: don't over-engineer early
  • • Establish clear ownership with CODEOWNERS
  • • Use semantic versioning strictly
  • • Document breaking changes thoroughly
  • • Provide migration guides for major versions
  • • Set up automated visual regression testing
  • • Create contribution guidelines early
  • • Use feature flags for gradual rollouts