Modern Theming Styling Implementation

1. CSS-in-JS Styled-components Emotion

Library Features Bundle Size Use Case
styled-components Tagged templates, theming, SSR support ~16KB Most popular, component-scoped styles, dynamic theming
Emotion Framework agnostic, better performance, smaller bundle ~8KB Faster than styled-components, works with React/Vue/Angular
Linaria Zero-runtime, extracts to CSS files at build time 0KB runtime Best performance, no runtime overhead, static extraction
Vanilla Extract Zero-runtime, TypeScript-first, type-safe styles 0KB runtime Type-safe theming, build-time extraction, excellent DX
Stitches Near-zero runtime, variants API, best-in-class DX ~6KB Modern API, great TypeScript support, variant-based styling

Example: CSS-in-JS Implementation

// styled-components
import styled from 'styled-components';

const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  
  &:hover {
    background-color: ${props => props.theme.colors.primaryDark};
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// Dynamic props
const Container = styled.div`
  display: flex;
  flex-direction: ${props => props.direction || 'row'};
  gap: ${props => props.gap || '1rem'};
  padding: ${props => props.padding || '0'};
`;

// Theming
import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#007bff',
    primaryDark: '#0056b3',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545'
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px'
  },
  breakpoints: {
    mobile: '768px',
    tablet: '1024px',
    desktop: '1440px'
  }
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Click me</Button>
      <Container direction="column" gap="2rem">
        {/* Content */}
      </Container>
    </ThemeProvider>
  );
}

// Responsive styling
const ResponsiveBox = styled.div`
  width: 100%;
  padding: 1rem;
  
  @media (min-width: ${props => props.theme.breakpoints.mobile}) {
    width: 50%;
    padding: 2rem;
  }
  
  @media (min-width: ${props => props.theme.breakpoints.desktop}) {
    width: 33.333%;
    padding: 3rem;
  }
`;

// Extending styles
const PrimaryButton = styled(Button)`
  background-color: ${props => props.theme.colors.primary};
`;

const SecondaryButton = styled(Button)`
  background-color: ${props => props.theme.colors.secondary};
`;

// Emotion (@emotion/styled)
import styled from '@emotion/styled';
import { css } from '@emotion/react';

const Button = styled.button`
  ${props => css`
    background: ${props.theme.colors.primary};
    padding: ${props.theme.spacing.md};
  `}
`;

// Emotion css prop
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

function MyComponent() {
  return (
    <div
      css={css`
        color: hotpink;
        &:hover {
          color: turquoise;
        }
      `}
    >
      Hello
    </div>
  );
}

// Stitches (near-zero runtime)
import { styled } from '@stitches/react';

const Button = styled('button', {
  backgroundColor: '$primary',
  padding: '$2 $4',
  borderRadius: '$md',
  
  variants: {
    variant: {
      primary: { backgroundColor: '$primary' },
      secondary: { backgroundColor: '$secondary' }
    },
    size: {
      sm: { padding: '$1 $2', fontSize: '$sm' },
      md: { padding: '$2 $4', fontSize: '$md' },
      lg: { padding: '$3 $6', fontSize: '$lg' }
    }
  },
  
  defaultVariants: {
    variant: 'primary',
    size: 'md'
  }
});

// Usage
<Button variant="secondary" size="lg">Click me</Button>

// Vanilla Extract (zero-runtime, type-safe)
// styles.css.ts
import { style, createTheme } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
  colors: {
    primary: '#007bff',
    secondary: '#6c757d'
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '24px'
  }
});

export const button = style({
  backgroundColor: vars.colors.primary,
  padding: `${vars.spacing.sm} ${vars.spacing.md}`,
  borderRadius: '4px',
  ':hover': {
    opacity: 0.8
  }
});

// Component
import { button, themeClass } from './styles.css';

function Button() {
  return <button className={button}>Click me</button>;
}

// Global styles (styled-components)
import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  
  body {
    font-family: ${props => props.theme.fonts.body};
    color: ${props => props.theme.colors.text};
    background: ${props => props.theme.colors.background};
  }
`;

function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />
      <Content />
    </ThemeProvider>
  );
}

CSS-in-JS Comparison

Library Runtime Performance
styled-components Runtime Good
Emotion Runtime Better
Linaria Zero Excellent
Vanilla Extract Zero Excellent
Stitches Near-zero Excellent

Benefits of CSS-in-JS

  • ✅ Component-scoped styles
  • ✅ Dynamic theming
  • ✅ No class name collisions
  • ✅ Dead code elimination
  • ✅ Type-safe styles (TypeScript)
  • ✅ Critical CSS automatic
  • ⚠️ Runtime overhead (except zero-runtime)
Modern Recommendation: Use Vanilla Extract or Stitches for new projects. Zero/near-zero runtime = best performance. styled-components for existing projects.

2. CSS Variables Custom Properties

Feature CSS Variables Sass Variables Advantage
Runtime Updates ✅ Yes ❌ No (compile-time) Dynamic theming, dark mode without re-compile
JavaScript Access ✅ getComputedStyle ❌ No Toggle themes with JS, reactive updates
Inheritance ✅ Cascades ❌ Static Scoped themes, component-level overrides
Browser Support 96%+ modern browsers N/A (compiles to CSS) Native, no preprocessor needed

Example: CSS Variables for Theming

// Define CSS variables in :root
:root {
  /* Colors */
  --color-primary: #007bff;
  --color-primary-dark: #0056b3;
  --color-primary-light: #66b3ff;
  --color-secondary: #6c757d;
  --color-success: #28a745;
  --color-danger: #dc3545;
  --color-warning: #ffc107;
  
  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  /* Typography */
  --font-family-base: 'Inter', -apple-system, sans-serif;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 18px;
  --font-weight-normal: 400;
  --font-weight-bold: 700;
  
  /* Borders */
  --border-radius-sm: 4px;
  --border-radius-md: 8px;
  --border-radius-lg: 12px;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
  
  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-base: 300ms ease;
  --transition-slow: 500ms ease;
}

// Using CSS variables
.button {
  background-color: var(--color-primary);
  color: white;
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--border-radius-md);
  font-family: var(--font-family-base);
  font-size: var(--font-size-md);
  transition: all var(--transition-fast);
  box-shadow: var(--shadow-sm);
}

.button:hover {
  background-color: var(--color-primary-dark);
  box-shadow: var(--shadow-md);
}

// Fallback values
.element {
  color: var(--color-text, #333); // Falls back to #333
  padding: var(--spacing-md, 16px);
}

// Scoped CSS variables (component-level)
.card {
  --card-padding: var(--spacing-lg);
  --card-background: white;
  --card-shadow: var(--shadow-md);
  
  padding: var(--card-padding);
  background: var(--card-background);
  box-shadow: var(--card-shadow);
}

.card.large {
  --card-padding: var(--spacing-xl);
}

// Dark mode with CSS variables
:root {
  --color-background: white;
  --color-text: #333;
  --color-border: #e0e0e0;
}

[data-theme="dark"] {
  --color-background: #1a1a1a;
  --color-text: #f0f0f0;
  --color-border: #404040;
}

// JavaScript: Toggle theme
function toggleTheme() {
  const root = document.documentElement;
  const currentTheme = root.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  root.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
}

// React: Get/Set CSS variables
function useCSSVariable(variable, defaultValue) {
  const [value, setValue] = useState(() => {
    const root = document.documentElement;
    return getComputedStyle(root).getPropertyValue(variable) || defaultValue;
  });
  
  const updateValue = (newValue) => {
    document.documentElement.style.setProperty(variable, newValue);
    setValue(newValue);
  };
  
  return [value, updateValue];
}

// Usage
function ColorPicker() {
  const [primaryColor, setPrimaryColor] = useCSSVariable('--color-primary', '#007bff');
  
  return (
    <input
      type="color"
      value={primaryColor}
      onChange={(e) => setPrimaryColor(e.target.value)}
    />
  );
}

// Calc with CSS variables
:root {
  --base-spacing: 8px;
}

.element {
  padding: calc(var(--base-spacing) * 2); // 16px
  margin: calc(var(--base-spacing) * 3);  // 24px
}

// Responsive CSS variables
:root {
  --container-padding: 1rem;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 2rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 3rem;
  }
}

.container {
  padding: var(--container-padding);
}

// CSS variables with HSL for color variations
:root {
  --primary-h: 211;
  --primary-s: 100%;
  --primary-l: 50%;
  
  --color-primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
  --color-primary-light: hsl(var(--primary-h), var(--primary-s), calc(var(--primary-l) + 10%));
  --color-primary-dark: hsl(var(--primary-h), var(--primary-s), calc(var(--primary-l) - 10%));
}

// Tailwind CSS with CSS variables
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: 'var(--color-primary)',
        secondary: 'var(--color-secondary)'
      },
      spacing: {
        'custom': 'var(--spacing-custom)'
      }
    }
  }
};

// Usage
<div className="bg-primary text-white p-custom">Content</div>

CSS Variables Best Practices

When to Use CSS Variables

  • ✅ Dark mode / theme switching
  • ✅ User-customizable colors
  • ✅ Responsive design tokens
  • ✅ Dynamic component theming
  • ✅ Design system tokens
  • ✅ Runtime theme updates
  • ❌ Complex calculations (use Sass)
Performance: CSS variables have zero runtime overhead and are faster than JavaScript-based theming. Perfect for dark mode and design systems.

3. Tailwind CSS Utility-first Design

Approach Pros Cons Use Case
Tailwind CSS Rapid development, consistent design, small production CSS Verbose HTML, learning curve, not semantic Prototyping, design systems, consistent styling
Traditional CSS Semantic class names, separation of concerns CSS bloat, naming conventions, specificity issues Custom designs, legacy projects
CSS Modules Scoped styles, traditional CSS Boilerplate, no utility classes Component-scoped styles

Example: Tailwind CSS Implementation

// Installation
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}'
  ],
  theme: {
    extend: {
      colors: {
        primary: '#007bff',
        secondary: '#6c757d'
      },
      spacing: {
        '128': '32rem',
        '144': '36rem'
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif']
      }
    }
  },
  plugins: []
};

// Basic usage
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Click me
</button>

// Responsive design
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <div className="bg-white p-4 rounded shadow">Card 1</div>
  <div className="bg-white p-4 rounded shadow">Card 2</div>
  <div className="bg-white p-4 rounded shadow">Card 3</div>
</div>

// Flexbox layout
<div className="flex items-center justify-between p-4 bg-gray-100">
  <div className="text-lg font-bold">Logo</div>
  <nav className="flex gap-4">
    <a href="#" className="text-gray-700 hover:text-blue-500">Home</a>
    <a href="#" className="text-gray-700 hover:text-blue-500">About</a>
  </nav>
</div>

// Dark mode
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Content
</div>

// State variants
<input
  type="text"
  className="border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 disabled:opacity-50"
/>

// Custom components with @apply
// styles.css
@layer components {
  .btn-primary {
    @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
  }
  
  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }
}

// React component
function Button({ children, variant = 'primary' }) {
  const baseClasses = 'font-bold py-2 px-4 rounded transition-colors';
  
  const variants = {
    primary: 'bg-blue-500 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-500 hover:bg-gray-700 text-white',
    outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white'
  };
  
  return (
    <button className={`${baseClasses} ${variants[variant]}`}>
      {children}
    </button>
  );
}

// Conditional classes with clsx
import clsx from 'clsx';

function Card({ featured, disabled }) {
  return (
    <div className={clsx(
      'p-4 rounded shadow',
      featured && 'border-2 border-blue-500',
      disabled && 'opacity-50 cursor-not-allowed'
    )}>
      Content
    </div>
  );
}

// Tailwind with CSS variables
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: 'rgb(var(--color-primary) / <alpha-value>)',
        secondary: 'rgb(var(--color-secondary) / <alpha-value>)'
      }
    }
  }
};

// CSS
:root {
  --color-primary: 0 123 255;
  --color-secondary: 108 117 125;
}

// Usage with opacity
<div className="bg-primary/50 text-primary">50% opacity</div>

// Arbitrary values
<div className="w-[137px] bg-[#1da1f2] top-[117px]">
  Custom values
</div>

// JIT mode (Just-in-Time)
// Generates only used classes, instant compile
<div className="mt-[137px]">Custom spacing</div>

// Plugins
// tailwind.config.js
module.exports = {
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
    require('@tailwindcss/line-clamp')
  ]
};

// Typography plugin
<article className="prose lg:prose-xl">
  <h1>Title</h1>
  <p>Automatically styled content</p>
</article>

Tailwind Benefits

  • ✅ No CSS naming conventions needed
  • ✅ Consistent design system
  • ✅ Tiny production CSS (3-10KB)
  • ✅ Rapid prototyping
  • ✅ Mobile-first responsive
  • ✅ Purges unused classes
  • ✅ Great with component libraries

Common Patterns

Pattern Classes
Center content flex items-center justify-center
Full height min-h-screen
Responsive grid grid grid-cols-1 md:grid-cols-3
Card bg-white rounded-lg shadow-md p-6
Production Ready: Tailwind CSS is used by GitHub, Shopify, Netflix. Production CSS is 3-10KB after purging unused classes.

4. Design Tokens Theme UI

Tool Purpose Format Use Case
Style Dictionary Transform design tokens to multiple platforms JSON → CSS/JS/iOS/Android Multi-platform design systems
Theme UI Constraint-based theming for React JavaScript theme object React apps with design constraints
Design Tokens W3C Standard format for design tokens JSON spec Industry standard, tool interoperability
Figma Tokens Sync design tokens with Figma Figma plugin → JSON Designer-developer collaboration

Example: Design Tokens Implementation

// Design tokens JSON
// tokens.json
{
  "color": {
    "brand": {
      "primary": { "value": "#007bff" },
      "secondary": { "value": "#6c757d" }
    },
    "semantic": {
      "success": { "value": "#28a745" },
      "danger": { "value": "#dc3545" },
      "warning": { "value": "#ffc107" }
    },
    "neutral": {
      "100": { "value": "#f8f9fa" },
      "200": { "value": "#e9ecef" },
      "900": { "value": "#212529" }
    }
  },
  "spacing": {
    "xs": { "value": "4px" },
    "sm": { "value": "8px" },
    "md": { "value": "16px" },
    "lg": { "value": "24px" },
    "xl": { "value": "32px" }
  },
  "typography": {
    "fontFamily": {
      "base": { "value": "Inter, system-ui, sans-serif" },
      "heading": { "value": "Poppins, sans-serif" },
      "mono": { "value": "Fira Code, monospace" }
    },
    "fontSize": {
      "xs": { "value": "12px" },
      "sm": { "value": "14px" },
      "base": { "value": "16px" },
      "lg": { "value": "18px" },
      "xl": { "value": "20px" },
      "2xl": { "value": "24px" }
    },
    "fontWeight": {
      "normal": { "value": "400" },
      "medium": { "value": "500" },
      "semibold": { "value": "600" },
      "bold": { "value": "700" }
    }
  },
  "borderRadius": {
    "sm": { "value": "4px" },
    "md": { "value": "8px" },
    "lg": { "value": "12px" },
    "full": { "value": "9999px" }
  },
  "shadow": {
    "sm": { "value": "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
    "md": { "value": "0 4px 6px -1px rgba(0, 0, 0, 0.1)" },
    "lg": { "value": "0 10px 15px -3px rgba(0, 0, 0, 0.1)" }
  }
}

// Style Dictionary config
// config.json
{
  "source": ["tokens.json"],
  "platforms": {
    "css": {
      "transformGroup": "css",
      "buildPath": "build/css/",
      "files": [{
        "destination": "variables.css",
        "format": "css/variables"
      }]
    },
    "js": {
      "transformGroup": "js",
      "buildPath": "build/js/",
      "files": [{
        "destination": "tokens.js",
        "format": "javascript/es6"
      }]
    }
  }
}

// Generated CSS variables
:root {
  --color-brand-primary: #007bff;
  --color-brand-secondary: #6c757d;
  --spacing-md: 16px;
  --font-family-base: Inter, system-ui, sans-serif;
}

// Theme UI
// theme.js
export const theme = {
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#007bff',
    secondary: '#6c757d',
    muted: '#f6f6f6'
  },
  fonts: {
    body: 'Inter, system-ui, sans-serif',
    heading: 'Poppins, sans-serif',
    monospace: 'Fira Code, monospace'
  },
  fontSizes: [12, 14, 16, 20, 24, 32, 48, 64],
  space: [0, 4, 8, 16, 24, 32, 64, 128, 256],
  breakpoints: ['40em', '52em', '64em'],
  radii: {
    small: 4,
    medium: 8,
    large: 16,
    round: 9999
  },
  shadows: {
    small: '0 1px 2px rgba(0, 0, 0, 0.05)',
    medium: '0 4px 6px rgba(0, 0, 0, 0.1)',
    large: '0 10px 15px rgba(0, 0, 0, 0.1)'
  }
};

// Theme UI usage
/** @jsxImportSource theme-ui */
import { ThemeProvider } from 'theme-ui';

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Box
        sx={{
          bg: 'primary',
          color: 'white',
          p: 3,
          borderRadius: 'medium',
          boxShadow: 'medium'
        }}
      >
        Themed Box
      </Box>
    </ThemeProvider>
  );
}

// Responsive values
<Box
  sx={{
    width: ['100%', '50%', '33.333%'],
    p: [2, 3, 4]
  }}
/>

// TypeScript theme
// theme.ts
export interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
  };
  spacing: {
    xs: string;
    sm: string;
    md: string;
    lg: string;
  };
}

export const lightTheme: Theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    background: '#ffffff',
    text: '#212529'
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px'
  }
};

export const darkTheme: Theme = {
  colors: {
    primary: '#66b3ff',
    secondary: '#9ca3af',
    background: '#1a1a1a',
    text: '#f0f0f0'
  },
  spacing: lightTheme.spacing
};

// Design tokens in components
import { theme } from './theme';

const Button = styled.button`
  background: ${theme.colors.primary};
  padding: ${theme.spacing.md};
  border-radius: ${theme.radii.medium};
  font-family: ${theme.fonts.body};
`;

Token Categories

  • Color: Brand, semantic, neutral scales
  • Spacing: Padding, margin, gap values
  • Typography: Font families, sizes, weights
  • Border: Radius, width, style
  • Shadow: Elevation levels
  • Animation: Duration, easing
  • Breakpoint: Responsive sizes

Design System Benefits

  • ✅ Consistent design across products
  • ✅ Single source of truth
  • ✅ Easy theme switching
  • ✅ Designer-developer sync
  • ✅ Multi-platform support
  • ✅ Scalable system
Design System Scale: Large companies like Airbnb, Shopify, IBM use design tokens to maintain consistency across 100+ products and platforms.

5. Dark Mode Light Mode Toggle

Implementation Method Persistence SSR Support
CSS Variables Toggle data-theme attribute localStorage ✅ Yes (with script)
Class Toggle Add/remove .dark class localStorage/cookie ✅ Yes
prefers-color-scheme CSS media query System preference ✅ Yes (automatic)
Context API React context provider localStorage ⚠️ Requires hydration

Example: Dark Mode Implementation

// CSS with prefers-color-scheme
:root {
  --color-bg: white;
  --color-text: #333;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #1a1a1a;
    --color-text: #f0f0f0;
  }
}

body {
  background: var(--color-bg);
  color: var(--color-text);
}

// Manual toggle with data-theme
:root[data-theme="light"] {
  --color-bg: white;
  --color-text: #333;
}

:root[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #f0f0f0;
}

// React: Dark mode hook
function useDarkMode() {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });
  
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return { theme, toggleTheme };
}

// Usage
function App() {
  const { theme, toggleTheme } = useDarkMode();
  
  return (
    <div>
      <button onClick={toggleTheme}>
        {theme === 'light' ? '🌙' : '☀️'}
      </button>
    </div>
  );
}

// Respect system preference
function useDarkMode() {
  const [theme, setTheme] = useState(() => {
    const stored = localStorage.getItem('theme');
    if (stored) return stored;
    
    // Check system preference
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  });
  
  // Listen for system preference changes
  useEffect(() => {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    
    const handleChange = (e) => {
      if (!localStorage.getItem('theme')) {
        setTheme(e.matches ? 'dark' : 'light');
      }
    };
    
    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);
  
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);
  
  return { theme, setTheme };
}

// Next.js: Prevent flash of wrong theme
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <script
            dangerouslySetInnerHTML={{
              __html: `
                (function() {
                  const theme = localStorage.getItem('theme') || 
                    (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
                  document.documentElement.setAttribute('data-theme', theme);
                })();
              `
            }}
          />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// next-themes library (recommended)
import { ThemeProvider } from 'next-themes';

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="data-theme" defaultTheme="system">
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

// Usage with next-themes
import { useTheme } from 'next-themes';

function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Toggle Theme
    </button>
  );
}

// Tailwind dark mode
// tailwind.config.js
module.exports = {
  darkMode: 'class', // or 'media'
  // ...
};

// Usage
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  Content adapts to theme
</div>

// Toggle dark class
function toggleDarkMode() {
  document.documentElement.classList.toggle('dark');
  const isDark = document.documentElement.classList.contains('dark');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
}

// Animated theme transition
html {
  transition: background-color 0.3s ease, color 0.3s ease;
}

// Three-way toggle (light/dark/system)
function ThemeSelector() {
  const { theme, setTheme } = useTheme();
  
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      <option value="light">☀️ Light</option>
      <option value="dark">🌙 Dark</option>
      <option value="system">💻 System</option>
    </select>
  );
}

Dark Mode Checklist

Color Considerations

  • Light Mode: High contrast, white backgrounds
  • Dark Mode: Not pure black (#1a1a1a better)
  • Contrast: Maintain WCAG AA (4.5:1)
  • Colors: Adjust saturation for dark mode
  • Images: Consider opacity or invert
  • Shadows: Lighter in dark mode
User Preference: 85% of users prefer dark mode in low-light environments. Always provide both options and respect system preferences.

6. Component Library Storybook Documentation

Tool Purpose Features Use Case
Storybook Component development environment Isolated development, documentation, testing, addons Build UI components in isolation, design system docs
Docusaurus Documentation website builder MDX, versioning, search, blog API docs, guides, design system documentation
Styleguidist React component documentation Auto-generated props, live editor React component libraries, simpler than Storybook
Ladle Lightweight Storybook alternative Vite-powered, faster, simpler Smaller projects, faster build times

Example: Storybook Setup

// Installation
npx storybook@latest init

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger']
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg']
    },
    disabled: {
      control: 'boolean'
    }
  }
};

export default meta;
type Story = StoryObj<typeof Button>;

// Stories
export const Primary: Story = {
  args: {
    children: 'Primary Button',
    variant: 'primary',
    size: 'md'
  }
};

export const Secondary: Story = {
  args: {
    children: 'Secondary Button',
    variant: 'secondary'
  }
};

export const Large: Story = {
  args: {
    children: 'Large Button',
    size: 'lg'
  }
};

export const Disabled: Story = {
  args: {
    children: 'Disabled Button',
    disabled: true
  }
};

// All variants
export const AllVariants: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: '1rem' }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
    </div>
  )
};

// With decorators
export const WithBackground: Story = {
  args: {
    children: 'Button on Dark Background'
  },
  decorators: [
    (Story) => (
      <div style={{ background: '#333', padding: '2rem' }}>
        <Story />
      </div>
    )
  ]
};

// Storybook configuration
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-themes'
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {}
  }
};

export default config;

// Theme configuration
// .storybook/preview.ts
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from '../src/theme';

export const decorators = [
  withThemeFromJSXProvider({
    themes: {
      light: lightTheme,
      dark: darkTheme
    },
    Provider: ThemeProvider
  })
];

// MDX documentation
// Button.mdx
import { Canvas, Meta, Story } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

# Button

A versatile button component with multiple variants and sizes.

## Usage

```jsx
import { Button } from '@/components/Button';

<Button variant="primary">Click me</Button>
```

## Variants

<Canvas of={ButtonStories.AllVariants} />

## Props

| Prop     | Type                              | Default   | Description          |
|----------|-----------------------------------|-----------|----------------------|
| variant  | 'primary' | 'secondary' | 'danger' | 'primary' | Button style variant |
| size     | 'sm' | 'md' | 'lg'              | 'md'      | Button size          |
| disabled | boolean                           | false     | Disable button       |

## Accessibility

- Uses semantic `button` element
- Supports keyboard navigation
- Includes focus styles
- Compatible with screen readers

// Testing with Storybook
// Button.test.tsx
import { composeStories } from '@storybook/react';
import { render, screen } from '@testing-library/react';
import * as stories from './Button.stories';

const { Primary, Disabled } = composeStories(stories);

test('renders primary button', () => {
  render(<Primary />);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

test('disabled button is not clickable', () => {
  render(<Disabled />);
  expect(screen.getByRole('button')).toBeDisabled();
});

// Interaction testing
// Button.stories.tsx
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';

export const ClickInteraction: Story = {
  args: {
    children: 'Click me'
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    await userEvent.click(button);
    await expect(button).toHaveFocus();
  }
};

// Visual regression testing
// Use Chromatic or Percy for visual testing
npm install --save-dev chromatic

// Build and publish
npm run build-storybook
npx chromatic --project-token=YOUR_TOKEN

Storybook Addons

  • Essentials: Docs, controls, actions, viewport
  • A11y: Accessibility testing
  • Interactions: Test user interactions
  • Themes: Theme switching
  • Links: Link stories together
  • Measure: Measure spacing
  • Outline: Show element outlines

Benefits

  • ✅ Isolated component development
  • ✅ Living documentation
  • ✅ Visual regression testing
  • ✅ Accessibility testing
  • ✅ Design system showcase
  • ✅ Component playground
  • ✅ Share with designers/PMs

Modern Theming & Styling Summary

  • CSS-in-JS: Use Vanilla Extract or Stitches for zero/near-zero runtime
  • CSS Variables: Perfect for dynamic theming and dark mode, zero overhead
  • Tailwind: Rapid development, consistent design, 3-10KB production CSS
  • Design Tokens: Single source of truth, multi-platform support
  • Dark Mode: CSS variables + localStorage + system preference
  • Documentation: Storybook for component libraries and design systems
Choose Wisely: Don't mix too many styling approaches. Pick one primary method (Tailwind OR CSS-in-JS OR CSS Modules) and stick with it for consistency.