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
Stride (Default) - Blue & Modern
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 */
}
Forest - Natural & Spacious
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 );
}
Runswap - Bold & Energetic
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 );
}
Acme - Professional & Tech
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:
Client Component
With State
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:
Root Layout
Conditional Brand
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:
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:
import { configureDynamicBrandSystem } from 'stride-ds' ;
configureDynamicBrandSystem ({
enableLocalStorage: false , // Disable persistence
});
How Brand Switching Works
Behind the scenes, applyBrandTheme() manipulates CSS classes:
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:
/* 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:
Stride
Coral
Forest
Runswap
Acme
< Button variant = "primary" > Continue </ Button >
Blue background (#0284c7)
Fully rounded (pill shape)
Outfit font
< Button variant = "primary" > Continue </ Button >
Orange background (#ea580c)
Square corners (0px radius)
Poppins font
< Button variant = "primary" > Continue </ Button >
Green background (#16a34a)
Small radius (4px)
Roboto font
Extra padding (1.2x spacing)
< Button variant = "primary" > Continue </ Button >
Hot pink background (#DA08A9)
Rounded (default)
Outfit font
Extra padding (1.2x spacing)
< Button variant = "primary" > Continue </ Button >
Indigo background (#4f46e5)
Medium rounded (12px)
Inter font
Creating Custom Brands
You can register custom brands dynamically at runtime:
Register Custom Brand
Full Token Override
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:
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
Test all brands during development
Add a brand switcher to your dev environment: if ( process . env . NODE_ENV === 'development' ) {
return < BrandSwitcher /> ;
}
Load brand fonts in the document head
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
Use semantic tokens, not brand-specific colors
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: { ... } });
Get the currently active brand ID const current = getCurrentBrand (); // 'stride' | 'coral' | ...
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