Design System Problems

Design Token Tree Shaking

January 15, 2026 • 5 min read

Design Token Tree Shaking

Design token tree shaking removes unused tokens from application bundles, delivering only the tokens actually referenced in code. Like tree shaking for JavaScript modules, token tree shaking analyzes usage and eliminates dead code. This optimization significantly reduces bundle sizes for applications using large token systems.

What Is Design Token Tree Shaking

Design token tree shaking is an optimization technique that analyzes application code to identify which tokens are used, then generates output containing only those tokens. Unused tokens never reach the final bundle, reducing file size without manual pruning.

Tree shaking automates what would otherwise require tedious manual tracking of token usage.

How Design Token Tree Shaking Works

Usage analysis:

function analyzeTokenUsage(codebase, allTokens) {
  const usedTokens = new Set();

  codebase.files.forEach(file => {
    const content = fs.readFileSync(file, 'utf8');

    allTokens.forEach(token => {
      const patterns = [
        `var(--${token.cssName})`,
        `tokens.${token.jsPath}`,
        `"${token.name}"`
      ];

      if (patterns.some(p => content.includes(p))) {
        usedTokens.add(token.name);

        // Include referenced tokens
        if (token.references) {
          token.references.forEach(ref => usedTokens.add(ref));
        }
      }
    });
  });

  return usedTokens;
}

Filtered output generation:

function generateTreeShakenOutput(allTokens, usedTokens) {
  const filteredTokens = allTokens.filter(token =>
    usedTokens.has(token.name)
  );

  return generateCSS(filteredTokens);
}

// Before: 450 tokens → 45KB
// After: 123 tokens → 12KB

Build integration:

// webpack.config.js
module.exports = {
  plugins: [
    new TokenTreeShakePlugin({
      tokensPath: './tokens',
      outputPath: './dist/tokens.css',
      analyze: ['./src/**/*.{js,jsx,ts,tsx,css,scss}']
    })
  ]
};

Key Considerations

Common Questions

How should reference chains be handled?

When a used token references other tokens, those references must also be included.

Reference resolution:

function resolveAllReferences(token, allTokens, included) {
  included.add(token.name);

  if (token.value && token.value.startsWith('{')) {
    const refName = token.value.slice(1, -1);
    const refToken = allTokens.find(t => t.name === refName);

    if (refToken && !included.has(refName)) {
      resolveAllReferences(refToken, allTokens, included);
    }
  }

  return included;
}

// Usage
const usedTokens = new Set();
directlyUsedTokens.forEach(token => {
  resolveAllReferences(token, allTokens, usedTokens);
});

Example chain:

// Used in code: color.action.primary
// Must include entire chain:
{
  "color.action.primary": "{color.brand.primary}",
  "color.brand.primary": "{color.primitive.blue.500}",
  "color.primitive.blue.500": "#3B82F6"
}

How can dynamic usage be addressed?

Dynamic token access patterns defeat static analysis.

Problematic patterns:

// Dynamic property access - cannot tree shake
const tokenName = `color.${variant}`;
const value = tokens[tokenName];

// Computed CSS variable - cannot analyze
const cssVar = `var(--color-${state})`;

Mitigation strategies:

Include all tokens matching pattern:

function handleDynamicPattern(pattern, allTokens) {
  const regex = new RegExp(pattern.replace('${variant}', '.*'));
  return allTokens.filter(t => regex.test(t.name));
}

// Include all color.* tokens if dynamic color access detected

Whitelist for dynamic usage:

const whitelist = [
  'color.status.*',  // Dynamic status colors
  'spacing.*'        // Dynamic spacing
];

Refactor to static usage:

// Instead of dynamic access
const colors = {
  primary: tokens.color.action.primary,
  secondary: tokens.color.action.secondary,
  destructive: tokens.color.action.destructive
};
const value = colors[variant];

What about CSS-only tree shaking?

CSS tokens present unique challenges since CSS is not module-based.

PurgeCSS integration:

// postcss.config.js
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.{js,jsx,html}'],
      css: ['./tokens.css'],
      safelist: {
        // Keep tokens that might be dynamically used
        greedy: [/^--color-status/]
      }
    })
  ]
};

Custom CSS tree shaking:

const postcss = require('postcss');

function treeShakeCSS(css, usedVariables) {
  return postcss.parse(css).nodes.filter(node => {
    if (node.type === 'decl' && node.prop.startsWith('--')) {
      return usedVariables.has(node.prop);
    }
    return true;
  });
}

Limitations:

Summary

Design token tree shaking removes unused tokens from bundles by analyzing code for token references. Implementation requires tracking reference chains to include transitively needed tokens. Dynamic usage patterns complicate analysis and may require whitelisting or code refactoring. CSS-specific tree shaking integrates with tools like PurgeCSS but has limitations due to CSS’s global scope. Effective tree shaking can dramatically reduce token bundle sizes.

Buoy scans your codebase for design system inconsistencies before they ship

Detect Design Drift Free
← Back to Token Management