Skip to main content

Overview

Stride Design System is built for customization. You can:
  • Override CSS variables to change colors, spacing, and sizing globally
  • Extend component variants using class-variance-authority (CVA)
  • Compose styles with the cn() utility
  • Create custom brands with full token control
  • Wrap components to add custom behavior
All without modifying the source code.

Overriding CSS Variables

The easiest way to customize is by overriding CSS custom properties:

Global Customization

/* In your app's CSS file */
:root {
  /* Change button radius globally */
  --radius-button: 0.5rem; /* 8px instead of full pill */
  
  /* Adjust spacing scale */
  --spacing-lg: 1.25rem; /* 20px instead of 16px */
  
  /* Custom shadow */
  --shadow-md: 0 8px 16px rgba(0, 0, 0, 0.15);
  
  /* Adjust button heights */
  --button-height-md: 3rem; /* 48px instead of 40px */
}

Brand-Specific Overrides

Customize tokens for specific brands:
Brand Overrides
/* Override only for Coral brand */
.brand-coral {
  --card-radius: var(--radius-md); /* Smaller cards for Coral */
  --spacing-scale: 1.1; /* Slightly more spacing */
}

/* Override for Forest brand */
.brand-forest {
  --button-height-md: 3.5rem; /* Taller buttons */
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); /* Softer shadow */
}

Component-Scoped Overrides

Override tokens for specific components:
Scoped Variable Override
function CustomButton() {
  return (
    <div style={{
      '--radius-button': '0.25rem', // Square buttons in this scope
      '--button-height-md': '3rem',
    }}>
      <Button>Custom Button</Button>
    </div>
  );
}

Using the cn() Utility

The cn() utility (clsx + tailwind-merge) intelligently combines Tailwind classes:
cn() Utility (utils.ts)
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Basic Usage

import { cn } from '@/lib/utils';

function Alert({ variant, className }) {
  return (
    <div
      className={cn(
        'rounded-lg p-4 border', // Base classes
        {
          'bg-red-50 border-red-200': variant === 'error',
          'bg-blue-50 border-blue-200': variant === 'info',
        },
        className // User overrides
      )}
    />
  );
}

Component Composition

Extend components with custom classes:
Extending Button
import { Button } from 'stride-ds';
import { cn } from '@/lib/utils';

function IconButton({ icon, children, className, ...props }) {
  return (
    <Button
      className={cn(
        'gap-2', // Add gap for icon
        '[--button-height-md:2.75rem]', // Custom height
        className
      )}
      {...props}
    >
      {icon}
      {children}
    </Button>
  );
}

// Usage
<IconButton icon={<StarIcon />}>Favorite</IconButton>

Creating Custom Variants

Extend existing variants using CVA (class-variance-authority):
import { Button } from 'stride-ds';
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const customButtonVariants = cva('', {
  variants: {
    gradient: {
      purple: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
      blue: 'bg-gradient-to-r from-blue-500 to-cyan-500 text-white',
    },
  },
});

function GradientButton({ gradient, className, ...props }) {
  return (
    <Button
      className={cn(
        customButtonVariants({ gradient }),
        className
      )}
      {...props}
    />
  );
}

// Usage
<GradientButton gradient="purple">Get Started</GradientButton>

Wrapping Components

Create wrapper components with custom defaults:
import { Button, type ButtonProps } from 'stride-ds';

export function PrimaryCTA(props: ButtonProps) {
  return (
    <Button
      variant="primary"
      size="lg"
      className="min-w-[200px] font-semibold"
      {...props}
    />
  );
}

// Usage - consistent CTA styling across app
<PrimaryCTA>Get Started Free</PrimaryCTA>

Custom Brand Creation

Create a completely custom brand dynamically:
import {
  registerDynamicBrand,
  applyDynamicBrandTheme,
} from 'stride-ds';

// Define your brand tokens
registerDynamicBrand({
  id: 'myapp',
  name: 'My App',
  description: 'Custom brand for My App',
  tokens: {
    core: {
      primary: {
        50: '#faf5ff',
        100: '#f3e8ff',
        200: '#e9d5ff',
        500: '#a855f7', // Main brand color
        600: '#9333ea',
        700: '#7e22ce',
        900: '#581c87',
      },
      neutral: {
        0: '#ffffff',
        50: '#fafafa',
        100: '#f5f5f5',
        200: '#e5e5e5',
        500: '#737373',
        900: '#171717',
      },
    },
    typography: {
      fontFamilyPrimary: '"DM Sans", sans-serif',
      fontFamilySecondary: '"Space Mono", monospace',
    },
    layout: {
      radiusButton: '0.5rem',  // 8px
      radiusCard: '1rem',      // 16px
      spacingScale: 1.15,      // 15% more spacing
    },
    semantic: {
      textPrimary: '#171717',
      bgPrimary: '#ffffff',
      interactivePrimary: '#9333ea',
      interactivePrimaryHover: '#7e22ce',
      borderFocus: '#a855f7',
    },
  },
  fallback: {
    brand: 'stride', // Use Stride for undefined tokens
  },
});

// Apply your custom brand
applyDynamicBrandTheme('myapp');

Theming Specific Components

Target individual component instances:
<Button
  style={{
    '--interactive-primary': '#7c3aed',
    '--interactive-primary-hover': '#6d28d9',
  }}
>
  Purple Button
</Button>

TypeScript Customization

Extend component prop types:
Custom Component Types
import { Button, type ButtonProps } from 'stride-ds';
import { type ReactNode } from 'react';

// Extend Button props
interface CustomButtonProps extends ButtonProps {
  loading?: boolean;
  icon?: ReactNode;
  badge?: string | number;
}

export function EnhancedButton({
  loading,
  icon,
  badge,
  children,
  ...props
}: CustomButtonProps) {
  return (
    <Button disabled={loading} {...props}>
      {loading ? <Spinner /> : icon}
      {children}
      {badge && (
        <span className="ml-2 px-2 py-0.5 bg-white/20 rounded-full text-xs">
          {badge}
        </span>
      )}
    </Button>
  );
}

// Full type safety
<EnhancedButton
  variant="primary"
  loading={isLoading}
  icon={<StarIcon />}
  badge={5}
>
  Notifications
</EnhancedButton>

Dark Mode Customization

Customize dark mode tokens:
Dark Mode Overrides
/* Custom dark mode colors */
html.dark,
.dark {
  /* Override background */
  --bg-primary: #0a0a0a; /* Deeper black */
  --bg-secondary: #1a1a1a;
  
  /* Override borders */
  --border-primary: #2a2a2a; /* Lighter borders in dark mode */
  
  /* Custom accent in dark mode */
  --interactive-primary: #8b5cf6; /* Brighter purple in dark */
  --interactive-primary-hover: #a78bfa;
}

/* Brand-specific dark mode */
html.dark.brand-coral {
  --bg-primary: #1c1410; /* Warm dark background for Coral */
  --interactive-primary: #fb923c; /* Brighter orange in dark */
}

Animation Customization

Customize component animations:
Custom Animations
/* Override button press animation */
.btn-bounce:active {
  animation: bounce 0.3s ease;
}

@keyframes bounce {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(0.95); }
}

/* Custom card hover */
.card-lift {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.card-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-xl);
}

/* Custom modal entrance */
.modal-slide-up {
  animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

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

Best Practices

Override tokens in :root or brand classes rather than hardcoding values:
/* ✅ Good - centralized customization */
:root {
  --radius-button: 0.5rem;
}

/* ❌ Bad - scattered hardcoded values */
.btn-1 { border-radius: 8px; }
.btn-2 { border-radius: 8px; }
.btn-3 { border-radius: 8px; }
Always use cn() when combining classes to avoid conflicts:
// ✅ Good - cn() handles conflicts
className={cn('p-4', className)}

// ❌ Bad - manual string concatenation
className={'p-4 ' + className}
If you customize components, test them with all brand themes:
['stride', 'coral', 'forest', 'runswap', 'acme'].forEach(brand => {
  applyBrandTheme(brand);
  // Test your component
});
Even when customizing, reference semantic tokens:
// ✅ Good - adapts to brands
<div className="bg-[var(--bg-secondary)]" />

// ❌ Bad - hardcoded color
<div className="bg-gray-100" />

Examples

E-commerce Button

<Button
  size="lg"
  className="min-w-[200px] font-bold uppercase tracking-wide"
  style={{
    '--radius-button': '0.25rem',
    '--button-height-lg': '3.5rem',
  }}
>
  Add to Cart
</Button>

Dashboard Card

<Card
  className="hover:shadow-xl transition-all duration-300"
  style={{
    '--card-radius': '1rem',
    '--card-padding-md': '2rem',
  }}
>
  <CardHeader>
    <h3>Revenue</h3>
  </CardHeader>
  <CardContent>
    <p className="text-4xl font-bold">$12,450</p>
  </CardContent>
</Card>

Auth Input

<Input
  type="password"
  placeholder="Enter password"
  className="font-mono"
  style={{
    '--input-height-md': '3.5rem',
    '--radius-button': '0.5rem',
  }}
/>

Notification Badge

<Badge
  variant="danger"
  className="animate-pulse"
  style={{
    '--status-danger': '#dc2626',
  }}
>
  5
</Badge>

Next Steps

Theming System

Understand the token architecture

Multi-brand

Create and manage multiple brands

Component Library

Explore all customizable components

API Reference

cn() utility and TypeScript types

Build docs developers (and LLMs) love