Brand as Code: Engineering Visual Identity Systems

A brand is not a mood board. It is a set of constraints that produce consistent output across every surface where your product exists.

Most brand work ends as a PDF. A style guide that gets ignored the moment the first engineer opens their editor. We build brand systems that live in the codebase — enforced by tooling, not by memory.


What "Brand as Code" Actually Means

A brand system in code has four properties:

  1. Parseable -- machines can read and validate it
  2. Single source of truth -- one change propagates everywhere
  3. Version-controlled -- the history of brand decisions is in git
  4. Composable -- pieces combine predictably without designer intervention

This is the difference between a color palette in Figma and a color palette in tokens.ts that feeds Tailwind, generates CSS custom properties, and exports a React Native StyleSheet simultaneously.


The Token Architecture

We structure tokens in three tiers. Each tier is more specific than the last. Nothing from a lower tier leaks into a higher tier.

// Tier 1: Primitive values — raw design decisions, no semantic meaning
const primitive = {
  color: {
    orange: {
      500: '#ED6D40',
      400: '#F08C66',
      600: '#D45A2F',
    },
    teal: {
      500: '#5C93A0',
      400: '#7AABB6',
      600: '#4A7D89',
    },
    dark: {
      950: '#0A0A0A',
      900: '#111111',
      800: '#1C1C1C',
      700: '#252422',
    },
    light: {
      100: '#FFFCF2',
      200: '#F0F2F5',
      300: '#DADADA',
      400: '#C4C7CC',
    },
  },
  font: {
    mono: '"IBM Plex Mono", monospace',
    sans: '"Inter", system-ui, sans-serif',
    display: '"Inter", system-ui, sans-serif',
  },
} as const;

// Tier 2: Semantic tokens — meaning without context
// References primitives, never raw values
const semantic = {
  color: {
    brand:       primitive.color.orange[500],
    brandSubtle: primitive.color.orange[400],
    accent:      primitive.color.teal[500],
    surface:     primitive.color.dark[800],
    surfaceRaised: primitive.color.dark[700],
    text:        primitive.color.light[100],
    textMuted:   primitive.color.light[400],
    border:      'rgba(255, 255, 255, 0.08)',
  },
} as const;

// Tier 3: Component tokens — scoped decisions for specific components
// References semantic tokens, never primitives
const component = {
  button: {
    primary: {
      bg:     semantic.color.brand,
      text:   primitive.color.dark[900],
      hover:  primitive.color.orange[600],
    },
    ghost: {
      bg:     'transparent',
      text:   semantic.color.textMuted,
      border: semantic.color.border,
    },
  },
  card: {
    bg:     semantic.color.surface,
    border: semantic.color.border,
  },
} as const;

The discipline: when you change primitive.color.orange[500], every component token that references semantic.color.brand updates automatically. No find-and-replace across 40 files.


CSS Custom Properties as the Runtime Layer

Tokens defined in TypeScript generate CSS custom properties at build time. This enables theming, dark/light mode transitions, and runtime overrides without JavaScript.

// scripts/generate-css-vars.ts
// Runs at build time, outputs to globals.css
import { semantic } from './tokens';

function generateCSSVars(obj: Record<string, string>, prefix = ''): string {
  return Object.entries(obj)
    .map(([key, value]) => `  --${prefix}${key}: ${value};`)
    .join('\n');
}

const vars = generateCSSVars(semantic.color, 'color-');
// Output: CSS file with --color-brand: #ED6D40; etc.
/* globals.css — generated, do not edit manually */
:root {
  --color-brand: #ED6D40;
  --color-accent: #5C93A0;
  --color-surface: #1C1C1C;
  --color-text: #FFFCF2;
  --color-text-muted: #C4C7CC;
  --color-border: rgba(255, 255, 255, 0.08);
}

Every Tailwind class that uses these values references the CSS variable, not a hardcoded hex. This means a theme change is a single CSS file update.


Typography as a System

Typography is not a font choice. It is a set of relationships between scale, weight, tracking, and line height.

// typescale.ts — every text variant defined once
export const type = {
  display: {
    size:       'clamp(3rem, 9vw, 9rem)',
    weight:     900,
    tracking:   '-0.04em',
    lineHeight: '0.88',
    transform:  'uppercase',
  },
  heading: {
    size:       'clamp(1.5rem, 4vw, 3.5rem)',
    weight:     800,
    tracking:   '-0.03em',
    lineHeight: '1.1',
    transform:  'uppercase',
  },
  body: {
    size:       '1rem',
    weight:     400,
    tracking:   '0',
    lineHeight: '1.65',
  },
  label: {
    size:       '0.6875rem',
    weight:     500,
    tracking:   '0.14em',
    lineHeight: '1',
    transform:  'uppercase',
    font:       'mono',
  },
  mono: {
    size:       '0.75rem',
    weight:     400,
    tracking:   '0.06em',
    lineHeight: '1.6',
    font:       'mono',
  },
} as const;

A <Text variant="label"> component uses these values directly. No one writes text-[11px] tracking-[0.14em] uppercase font-mono in a component. They write <Text variant="label">.


Motion as Brand

Motion is the most underspecified part of most brand systems. "Smooth" and "snappy" are not engineering specs.

// motion.ts — every animation defined as data
export const motion = {
  duration: {
    instant:  100,  // immediate feedback: button press, checkbox
    fast:     200,  // micro-interactions: hover states, tooltips
    base:     350,  // standard transitions: panel open, card expand
    slow:     500,  // emphasis: modal enter, page transition
    crawl:    800,  // dramatic: hero animations, number counters
  },
  easing: {
    // Standard: most UI transitions
    standard: 'cubic-bezier(0.4, 0, 0.2, 1)',
    // Enter: elements arriving from off-screen
    enter:    'cubic-bezier(0.0, 0, 0.2, 1)',
    // Exit: elements leaving the screen
    exit:     'cubic-bezier(0.4, 0, 1, 1)',
    // Spring: playful, physical feel
    spring:   'cubic-bezier(0.34, 1.56, 0.64, 1)',
  },
} as const;
/* Tailwind extension from motion tokens */
.animate-enter {
  animation: slideUp 350ms cubic-bezier(0.0, 0, 0.2, 1) forwards;
}

.animate-exit {
  animation: fadeOut 200ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

@keyframes slideUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

Every animation in the product uses values from this table. No transition: all 0.3s ease anywhere.


Linting the Brand

The most dangerous phrase in a codebase is "just this once." We enforce token usage with ESLint.

// eslint-plugin-brand-tokens (custom rule)
// Flags hardcoded hex values in className props
module.exports = {
  rules: {
    'no-raw-colors': {
      create(context) {
        return {
          JSXAttribute(node) {
            if (node.name.name !== 'className') return;
            const value = node.value?.value ?? '';
            const hexPattern = /#[0-9a-fA-F]{3,8}/g;
            const matches = value.match(hexPattern);
            if (matches) {
              context.report({
                node,
                message: `Raw color value "${matches[0]}" found. Use a token instead (e.g. bg-brand, text-neutral-200).`,
              });
            }
          },
        };
      },
    },
  },
};

CI fails on any hardcoded color. The brand is enforced at the pull request level, not in a design review.


What a Brand System Ships With

When we deliver a brand ecosystem to a client, it includes:

  • Token files in TypeScript, JSON (for Figma tokens plugin), and CSS custom properties
  • Tailwind config wired to the token layer
  • Type system with a <Text> component, heading scale, and mono utility
  • Motion system with named durations and easings exported as Tailwind extensions
  • ESLint plugin to enforce token usage over raw values
  • Storybook with a token viewer showing every color, spacing value, and animation

The brand lives in git. It ships with the product. It evolves through pull requests.

That is the difference between a brand guide and a brand system.

Talk To The
Brew Team.

I'M A  [SELECT ONE]

ABOUT YOU

YOUR PROJECT