Skip to main content

Overview

Stride Design System supports white-label architecture out of the box. Ship the same React components with different visual identities by switching brand themes at runtime. Five pre-built brands included:
  • Stride - Modern blue (default)
  • Coral - Warm orange with sharp edges
  • Forest - Natural green with generous spacing
  • Runswap - Vibrant pink/magenta
  • Acme - Professional indigo

Brand Definitions

Brands are defined in TypeScript with colors managed in CSS:
Brand Registry (brands.ts)
export interface BrandTheme {
  id: string;
  name: string;
  description: string;
}

export const strideBrand: BrandTheme = {
  id: 'stride',
  name: 'Stride',
  description: 'Default Stride Design System brand with blue primary colors',
};

export const coralBrand: BrandTheme = {
  id: 'coral',
  name: 'Coral',
  description: 'Coral theme with warm orange-red primary colors and no button radius',
};

export const forestBrand: BrandTheme = {
  id: 'forest',
  name: 'Forest',
  description: 'Forest theme with natural green primary colors and generous spacing',
};

export const runswapBrand: BrandTheme = {
  id: 'runswap',
  name: 'Runswap',
  description: 'Runswap theme with purple primary colors',
};

export const acmeBrand: BrandTheme = {
  id: 'acme',
  name: 'Acme',
  description: 'Acme theme with indigo primary colors and modern tech aesthetic',
};

Brand Characteristics

Primary Color: Sky blue (#0ea5e9)Typography:
  • Primary: Outfit
  • Secondary: Inter
Visual Style:
  • Fully rounded buttons (pill-shaped)
  • Large card radius (24px)
  • Balanced spacing
  • Cool slate neutrals
.brand-stride {
  --brand-primary-500: #0ea5e9;
  --font-family-primary: 'Outfit', sans-serif;
  --radius-button: var(--radius-full);
}
Primary Color: Vibrant orange (#f97316)Typography:
  • Primary: Poppins
  • Secondary: Open Sans
Visual Style:
  • Sharp corners (no button radius!)
  • Warm-tinted neutrals
  • Friendly, approachable feel
  • Ideal for consumer apps
.brand-coral {
  --brand-primary-500: #f97316;
  --font-family-primary: 'Poppins', sans-serif;
  --radius-button: 0; /* Square buttons */
  --brand-neutral-50: #fefaf8; /* Warm tint */
}
Primary Color: Emerald green (#22c55e)Typography:
  • Primary: Roboto
  • Secondary: Source Sans Pro
Visual Style:
  • Generous spacing (1.2x scale)
  • Small border radius (4px cards, 2px buttons)
  • Cool gray neutrals
  • Eco-friendly aesthetic
.brand-forest {
  --brand-primary-500: #22c55e;
  --font-family-primary: 'Roboto', sans-serif;
  --spacing-scale: 1.2; /* 20% larger spacing */
  --radius-button: var(--radius-sm);
  --card-radius: var(--radius-md);
}
Primary Color: Hot pink (#F60CBF)Typography:
  • Primary: Outfit
  • Secondary: Nunito
Visual Style:
  • Bold magenta primary
  • Generous spacing (1.2x scale)
  • Modern, energetic feel
  • Great for fitness/sports apps
.brand-runswap {
  --brand-primary-500: #F60CBF;
  --font-family-primary: 'Outfit', sans-serif;
  --spacing-scale: 1.2;
  --text-brand: var(--brand-primary-500);
}
Primary Color: Indigo (#6366f1)Typography:
  • Primary: Inter
  • Secondary: JetBrains Mono (monospace!)
Visual Style:
  • Rounded buttons (12px)
  • Tech-forward aesthetic
  • Cool blue-tinted neutrals
  • Perfect for developer tools
.brand-acme {
  --brand-primary-500: #6366f1;
  --font-family-primary: 'Inter', sans-serif;
  --font-family-secondary: 'JetBrains Mono', monospace;
  --radius-button: var(--radius-lg);
  --card-radius: var(--radius-xl);
}

Switching Brands

Method 1: applyBrandTheme() Function

Switch brands programmatically at runtime:
import { applyBrandTheme } from 'stride-ds';

function BrandSwitcher() {
  return (
    <div className="flex gap-2">
      <button onClick={() => applyBrandTheme('stride')}>
        Stride
      </button>
      <button onClick={() => applyBrandTheme('coral')}>
        Coral
      </button>
      <button onClick={() => applyBrandTheme('forest')}>
        Forest
      </button>
      <button onClick={() => applyBrandTheme('runswap')}>
        Runswap
      </button>
      <button onClick={() => applyBrandTheme('acme')}>
        Acme
      </button>
    </div>
  );
}

Method 2: BrandInitializer Component

Set the initial brand at app startup:
import { BrandInitializer } from 'stride-ds';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <BrandInitializer brand="forest" />
        {children}
      </body>
    </html>
  );
}

Brand Persistence

Brand selection is automatically saved to localStorage:
Auto-persistence
import { applyBrandTheme, initializeBrand } from 'stride-ds';

// Set brand (automatically saves to localStorage)
applyBrandTheme('coral');

// On next page load, restore saved brand
useEffect(() => {
  initializeBrand(); // Reads from localStorage
}, []);
Disable persistence if needed:
Disable localStorage
import { configureDynamicBrandSystem } from 'stride-ds';

configureDynamicBrandSystem({
  enableLocalStorage: false, // Disable persistence
});

How Brand Switching Works

Behind the scenes, applyBrandTheme() manipulates CSS classes:
Internal Implementation
export const applyBrandTheme = (brandId: string): void => {
  const root = document.documentElement;
  
  // Add transition class
  root.classList.add('brand-switching');
  
  // Remove old brand classes
  availableBrands.forEach(brand => {
    root.classList.remove(`brand-${brand.id}`);
  });
  
  // Apply new brand class
  root.classList.add(`brand-${brandId}`);
  
  // Save to localStorage
  localStorage.setItem('stride-brand', brandId);
  
  // Remove transition class after animation
  setTimeout(() => {
    root.classList.remove('brand-switching');
  }, 50);
};
This triggers CSS cascade:
Brand Class Selectors
/* Stride colors */
.brand-stride {
  --brand-primary-500: #0ea5e9;
}

/* Forest colors */
.brand-forest {
  --brand-primary-500: #22c55e;
}

/* All components reference semantic tokens */
.button-primary {
  background: var(--interactive-primary); /* Maps to --brand-primary-600 */
}

Visual Comparison

Here’s how the same Button component looks across brands:
<Button variant="primary">Continue</Button>
  • Blue background (#0284c7)
  • Fully rounded (pill shape)
  • Outfit font

Creating Custom Brands

You can register custom brands dynamically at runtime:
import { registerDynamicBrand, applyDynamicBrandTheme } from 'stride-ds';

// Define your brand
registerDynamicBrand({
  id: 'mycompany',
  name: 'My Company',
  description: 'Custom brand for My Company',
  tokens: {
    core: {
      primary: {
        500: '#7c3aed', // Violet
        600: '#6d28d9',
        700: '#5b21b6',
      },
    },
    typography: {
      fontFamilyPrimary: 'Helvetica, sans-serif',
    },
    layout: {
      radiusButton: '8px',
    },
  },
  fallback: {
    brand: 'stride', // Fallback for undefined tokens
  },
});

// Apply your custom brand
applyDynamicBrandTheme('mycompany');
Custom brands are automatically saved to localStorage and restored on page reload.

Multi-tenant Applications

Perfect for SaaS apps where each customer needs their own branding:
Tenant-based Branding
import { useEffect } from 'react';
import { registerDynamicBrand, applyDynamicBrandTheme } from 'stride-ds';

function TenantApp({ tenantId, tenantConfig }) {
  useEffect(() => {
    // Register tenant's custom brand
    registerDynamicBrand({
      id: `tenant-${tenantId}`,
      name: tenantConfig.brandName,
      tokens: {
        core: {
          primary: tenantConfig.colors.primary,
          neutral: tenantConfig.colors.neutral,
        },
        typography: {
          fontFamilyPrimary: tenantConfig.fonts.primary,
        },
      },
    });
    
    // Apply tenant's brand
    applyDynamicBrandTheme(`tenant-${tenantId}`);
  }, [tenantId]);
  
  return <YourApp />;
}

Best Practices

Add a brand switcher to your dev environment:
if (process.env.NODE_ENV === 'development') {
  return <BrandSwitcher />;
}
Ensure all brand fonts are loaded:
import { Inter, Outfit, Poppins, Roboto } from 'next/font/google';

const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
const outfit = Outfit({ subsets: ['latin'], variable: '--font-outfit' });
// ... load all brand fonts
Let components adapt automatically:
// ❌ Bad - hardcoded to Stride blue
<div className="bg-[#0ea5e9]" />

// ✅ Good - adapts to active brand
<div className="bg-[var(--interactive-primary)]" />

API Reference

applyBrandTheme
(brandId: string) => void
Switch to a pre-built brand theme
applyBrandTheme('coral');
registerDynamicBrand
(config: DynamicBrandConfig) => void
Register a new custom brand at runtime
registerDynamicBrand({ id: 'custom', name: 'Custom', tokens: {...} });
getCurrentBrand
() => string
Get the currently active brand ID
const current = getCurrentBrand(); // 'stride' | 'coral' | ...
initializeBrand
() => void
Restore brand from localStorage (call on app startup)
useEffect(() => {
  initializeBrand();
}, []);

Next Steps

Theming System

Understand how CSS variables power the brand system

Customization

Create your own brand theme from scratch

Component Library

See how components adapt to each brand

API Reference

Full TypeScript API documentation

Build docs developers (and LLMs) love