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>
);
}
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>
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>
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};
`;
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>
);
}
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
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.