Modern Styling Implementation Patterns
1. CSS-in-JS Styled-components Emotion
CSS-in-JS libraries enable writing CSS directly in JavaScript with dynamic styling, scoped styles, and TypeScript support.
| Library | Features | Use Case | Bundle Impact |
|---|---|---|---|
| styled-components | Tagged templates, theming, SSR, dynamic props, attrs | React apps with dynamic theming | ~15KB gzipped |
| Emotion | css prop, styled API, composition, framework agnostic | Performance-critical apps | ~7KB gzipped |
| Styled JSX | Next.js built-in, scoped styles, zero runtime | Next.js projects | Build-time only |
| Linaria | Zero-runtime, CSS extraction, static styles | Performance-first projects | 0KB runtime |
Example: Styled-components with dynamic theming
// theme.ts
export const theme = {
colors: {
primary: '#007acc',
secondary: '#6c757d',
danger: '#dc3545'
},
spacing: (n: number) => `${n * 8}px`
};
// Button.tsx
import styled from 'styled-components';
const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
padding: ${props => props.theme.spacing(2)};
background: ${props => props.theme.colors[props.variant || 'primary']};
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
// App.tsx
import { ThemeProvider } from 'styled-components';
function App() {
return (
<ThemeProvider theme={theme}>
<Button variant="primary">Submit</Button>
<Button variant="secondary">Cancel</Button>
</ThemeProvider>
);
}
Example: Emotion with css prop (faster than styled-components)
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const buttonStyle = css`
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
transition: transform 0.2s;
&:hover {
transform: translateY(-2px);
}
`;
function Button({ children }) {
return <button css={buttonStyle}>{children}</button>;
}
// Composition
const primaryButton = css`
${buttonStyle};
background: #007acc;
`;
const dangerButton = css`
${buttonStyle};
background: #dc3545;
`;
Note: Styled-components offers better DX with DevTools and component names in DOM. Emotion is
50% smaller and faster but requires css prop setup. Linaria for zero-runtime SSG sites.
2. Tailwind CSS Utility-first Design
Utility-first CSS framework for rapid UI development with consistent design system and minimal custom CSS.
| Category | Utilities | Example | Output CSS |
|---|---|---|---|
| Layout | flex, grid, container, box-sizing | flex items-center justify-between |
display: flex; align-items: center; justify-content: space-between |
| Spacing | p-*, m-*, space-*, gap-* | p-4 m-2 gap-6 |
padding: 1rem; margin: 0.5rem; gap: 1.5rem |
| Typography | text-*, font-*, leading-*, tracking-* | text-xl font-bold text-gray-900 |
font-size: 1.25rem; font-weight: 700; color: #111827 |
| Colors | bg-*, text-*, border-*, from-*, to-* | bg-blue-500 text-white |
background: #3b82f6; color: #ffffff |
| Responsive | sm:*, md:*, lg:*, xl:*, 2xl:* | sm:w-full md:w-1/2 lg:w-1/3 |
Media query breakpoints: 640px, 768px, 1024px, 1280px, 1536px |
| States | hover:*, focus:*, active:*, disabled:* | hover:bg-blue-600 focus:ring-2 |
Pseudo-class variants |
Example: Tailwind configuration and custom utilities
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
brand: {
50: '#f0f9ff',
500: '#0ea5e9',
900: '#0c4a6e'
}
},
spacing: {
'18': '4.5rem',
'88': '22rem'
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif']
}
}
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio')
]
};
// Component usage
function Card() {
return (
<div className="bg-white rounded-lg shadow-lg p-6 hover:shadow-xl transition-shadow">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Card Title</h2>
<p className="text-gray-600 leading-relaxed">Card content</p>
<button className="mt-4 px-6 py-2 bg-brand-500 text-white rounded-md
hover:bg-brand-600 focus:ring-2 focus:ring-brand-500
focus:ring-offset-2 disabled:opacity-50">
Action
</button>
</div>
);
}
Example: Responsive design with Tailwind
// Mobile-first responsive layout
function ResponsiveGrid() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="bg-gray-100 p-4 rounded-lg">Item 1</div>
<div className="bg-gray-100 p-4 rounded-lg">Item 2</div>
<div className="bg-gray-100 p-4 rounded-lg">Item 3</div>
</div>
);
}
// Dark mode support
function DarkModeButton() {
return (
<button className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white
border border-gray-300 dark:border-gray-700 px-4 py-2 rounded-md">
Toggle
</button>
);
}
Tailwind Best Practices: Use
@apply for repeated patterns, configure PurgeCSS
for production builds (~3KB final CSS), use JIT mode for instant compilation, leverage plugins ecosystem.
3. CSS Variables Custom Properties
Native CSS custom properties for dynamic theming, runtime updates, and component-scoped styling without JavaScript overhead.
| Feature | Syntax | Scope | Use Case |
|---|---|---|---|
| Definition | --variable-name: value; |
:root, element, inline | Define reusable values |
| Usage | var(--variable-name, fallback) |
Any CSS property | Apply variable with fallback |
| Inheritance | Cascades to descendants | DOM tree | Component theming |
| JavaScript Access | getComputedStyle(), setProperty() |
Runtime | Dynamic theme switching |
| Media Queries | Change values per breakpoint | Responsive | Adaptive spacing/sizing |
Example: Design system with CSS variables
/* styles/variables.css */
:root {
/* Colors */
--color-primary: #007acc;
--color-secondary: #6c757d;
--color-success: #28a745;
--color-danger: #dc3545;
--color-warning: #ffc107;
/* Spacing scale */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
/* Typography */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'Fira Code', monospace;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
/* 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);
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
}
/* Component styles */
.button {
padding: var(--space-md) var(--space-lg);
background: var(--color-primary);
color: white;
border: none;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
transition: all var(--transition-base);
box-shadow: var(--shadow-sm);
}
.button:hover {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.card {
padding: var(--space-lg);
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
}
Example: Runtime theme switching with JavaScript
// themes.ts
export const lightTheme = {
'--color-bg': '#ffffff',
'--color-text': '#1a1a1a',
'--color-border': '#e5e5e5'
};
export const darkTheme = {
'--color-bg': '#1a1a1a',
'--color-text': '#ffffff',
'--color-border': '#333333'
};
// ThemeProvider.tsx
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
useEffect(() => {
const root = document.documentElement;
const themeVars = theme === 'light' ? lightTheme : darkTheme;
Object.entries(themeVars).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Component usage
function ThemedCard() {
return (
<div style={{
background: 'var(--color-bg)',
color: 'var(--color-text)',
border: '1px solid var(--color-border)'
}}>
Themed content
</div>
);
}
Browser Support: Excellent - All modern browsers support CSS
custom properties. Use PostCSS plugins for older browser fallbacks.
4. Design Tokens Figma Integration
Design tokens are platform-agnostic design decisions (colors, spacing, typography) synced between design tools and code.
| Tool/Format | Purpose | Integration | Output |
|---|---|---|---|
| Figma Tokens Plugin | Extract tokens from Figma designs | JSON export, GitHub sync | tokens.json with color, spacing, typography |
| Style Dictionary | Transform tokens to platform formats | Build system integration | CSS, SCSS, JS, iOS, Android |
| Tokens Studio | Manage multi-brand token sets | Figma plugin with Git sync | Semantic + alias tokens |
| Theo (Salesforce) | Design token transformation | CLI, Node API | Multiple formats from single source |
Example: Design tokens structure (tokens.json)
{
"color": {
"brand": {
"primary": { "value": "#007acc", "type": "color" },
"secondary": { "value": "#6c757d", "type": "color" }
},
"semantic": {
"success": { "value": "{color.brand.primary}", "type": "color" },
"danger": { "value": "#dc3545", "type": "color" }
}
},
"spacing": {
"xs": { "value": "4px", "type": "spacing" },
"sm": { "value": "8px", "type": "spacing" },
"md": { "value": "16px", "type": "spacing" },
"lg": { "value": "24px", "type": "spacing" },
"xl": { "value": "32px", "type": "spacing" }
},
"typography": {
"fontFamily": {
"sans": { "value": "Inter, system-ui, sans-serif", "type": "fontFamily" },
"mono": { "value": "Fira Code, monospace", "type": "fontFamily" }
},
"fontSize": {
"sm": { "value": "14px", "type": "fontSize" },
"base": { "value": "16px", "type": "fontSize" },
"lg": { "value": "20px", "type": "fontSize" },
"xl": { "value": "24px", "type": "fontSize" }
},
"fontWeight": {
"regular": { "value": "400", "type": "fontWeight" },
"medium": { "value": "500", "type": "fontWeight" },
"bold": { "value": "700", "type": "fontWeight" }
}
},
"borderRadius": {
"sm": { "value": "4px", "type": "borderRadius" },
"md": { "value": "8px", "type": "borderRadius" },
"lg": { "value": "16px", "type": "borderRadius" },
"full": { "value": "9999px", "type": "borderRadius" }
}
}
Example: Style Dictionary configuration and build
// style-dictionary.config.js
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'src/styles/',
files: [{
destination: 'variables.css',
format: 'css/variables'
}]
},
js: {
transformGroup: 'js',
buildPath: 'src/tokens/',
files: [{
destination: 'tokens.js',
format: 'javascript/es6'
}]
},
typescript: {
transformGroup: 'js',
buildPath: 'src/tokens/',
files: [{
destination: 'tokens.ts',
format: 'typescript/es6-declarations'
}]
}
}
};
// Generated CSS output (variables.css)
:root {
--color-brand-primary: #007acc;
--color-brand-secondary: #6c757d;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--font-family-sans: Inter, system-ui, sans-serif;
--font-size-base: 16px;
--font-weight-bold: 700;
--border-radius-md: 8px;
}
// Generated TypeScript output (tokens.ts)
export const tokens = {
color: {
brand: {
primary: '#007acc',
secondary: '#6c757d'
}
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px'
}
};
Workflow: Design in Figma → Export tokens via plugin → Transform with Style Dictionary →
Generate CSS/JS/iOS/Android → Import in codebase. Automate with CI/CD for design-dev sync.
5. Dark Mode Light Mode Toggle
Implement theme switching with system preference detection, persistent storage, and smooth transitions.
| Approach | Implementation | Pros | Cons |
|---|---|---|---|
| CSS Media Query | @media (prefers-color-scheme: dark) |
Native, automatic, no JS | No manual toggle |
| Data Attribute | [data-theme="dark"] on root |
Easy CSS targeting, semantic | Requires JS for toggle |
| Class Toggle | .dark class on root |
Simple, Tailwind compatible | Flash of wrong theme (FOUT) |
| CSS Variables | Change variable values dynamically | Smooth transitions, runtime | More complex setup |
Example: Complete dark mode implementation with persistence
// theme.css
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-border: #e5e5e5;
--color-card-bg: #f8f9fa;
}
[data-theme="dark"] {
--color-bg: #1a1a1a;
--color-text: #ffffff;
--color-border: #333333;
--color-card-bg: #2d2d2d;
}
body {
background: var(--color-bg);
color: var(--color-text);
transition: background-color 0.3s ease, color 0.3s ease;
}
// useTheme.ts hook
import { useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
export function useTheme() {
const [theme, setTheme] = useState<Theme>(() => {
const stored = localStorage.getItem('theme') as Theme;
return stored || 'system';
});
useEffect(() => {
const root = document.documentElement;
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)');
const applyTheme = (newTheme: Theme) => {
let appliedTheme: 'light' | 'dark';
if (newTheme === 'system') {
appliedTheme = systemPreference.matches ? 'dark' : 'light';
} else {
appliedTheme = newTheme;
}
root.setAttribute('data-theme', appliedTheme);
localStorage.setItem('theme', newTheme);
};
applyTheme(theme);
// Listen for system preference changes
const handleChange = (e: MediaQueryListEvent) => {
if (theme === 'system') {
root.setAttribute('data-theme', e.matches ? 'dark' : 'light');
}
};
systemPreference.addEventListener('change', handleChange);
return () => systemPreference.removeEventListener('change', handleChange);
}, [theme]);
return { theme, setTheme };
}
// ThemeToggle.tsx component
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<div className="theme-toggle">
<button
onClick={() => setTheme('light')}
aria-pressed={theme === 'light'}
>
☀️ Light
</button>
<button
onClick={() => setTheme('dark')}
aria-pressed={theme === 'dark'}
>
🌙 Dark
</button>
<button
onClick={() => setTheme('system')}
aria-pressed={theme === 'system'}
>
💻 System
</button>
</div>
);
}
Example: Prevent flash of wrong theme (critical inline script)
<!-- Place in <head> before any CSS -->
<script>
(function() {
const theme = localStorage.getItem('theme') || 'system';
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches;
let appliedTheme;
if (theme === 'system') {
appliedTheme = systemPreference ? 'dark' : 'light';
} else {
appliedTheme = theme;
}
document.documentElement.setAttribute('data-theme', appliedTheme);
})();
</script>
<!-- Next.js implementation in _document.tsx -->
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head />
<body>
<script dangerouslySetInnerHTML={{
__html: `
(function() {
const theme = localStorage.getItem('theme') || 'system';
const dark = theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
})();
`
}} />
<Main />
<NextScript />
</body>
</Html>
);
}
Performance: Avoid transition: all on theme toggle (causes layout thrashing). Target specific
properties: background-color, color, border-color. Use prefers-reduced-motion for accessibility.
6. Component Library MUI Chakra UI
Production-ready component libraries with theming, accessibility, and extensive component catalogs.
| Library | Key Features | Best For | Bundle Size |
|---|---|---|---|
| Material-UI (MUI) | Material Design, comprehensive components, theming system | Enterprise apps, admin panels | ~80KB core + components |
| Chakra UI | Accessible, composable, dark mode, styled-system | Modern apps, fast development | ~45KB + components |
| Ant Design | Enterprise UI, rich components, internationalization | Data-heavy dashboards | ~60KB core + components |
| Mantine | Full-featured, hooks library, 120+ components | Complete solution, TypeScript | ~50KB + components |
| Radix UI | Unstyled, accessible primitives, headless | Custom designs, full control | Minimal, per-component |
| shadcn/ui | Copy-paste components, Radix + Tailwind | Full customization, no dependencies | Only what you use |
Example: Material-UI (MUI) with custom theme
// theme.ts
import { createTheme } from '@mui/material/styles';
export const theme = createTheme({
palette: {
primary: {
main: '#007acc',
light: '#42a5f5',
dark: '#005fa3'
},
secondary: {
main: '#6c757d'
},
mode: 'light' // or 'dark'
},
typography: {
fontFamily: 'Inter, system-ui, sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 700
}
},
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
textTransform: 'none',
boxShadow: 'none'
}
},
defaultProps: {
disableElevation: true
}
}
}
});
// App.tsx
import { ThemeProvider } from '@mui/material/styles';
import { Button, TextField, Card, CardContent } from '@mui/material';
function App() {
return (
<ThemeProvider theme={theme}>
<Card>
<CardContent>
<TextField
label="Email"
variant="outlined"
fullWidth
margin="normal"
/>
<Button variant="contained" color="primary">
Submit
</Button>
</CardContent>
</Card>
</ThemeProvider>
);
}
Example: Chakra UI with composition and dark mode
// App.tsx
import { ChakraProvider, extendTheme, Box, Button, Input, Stack } from '@chakra-ui/react';
const theme = extendTheme({
colors: {
brand: {
50: '#e3f2fd',
500: '#007acc',
900: '#003d66'
}
},
config: {
initialColorMode: 'light',
useSystemColorMode: true
}
});
function App() {
return (
<ChakraProvider theme={theme}>
<Box p={8} bg="gray.50" minH="100vh">
<Stack spacing={4} maxW="md">
<Input placeholder="Email" size="lg" />
<Button
colorScheme="brand"
size="lg"
_hover={{ transform: 'translateY(-2px)', boxShadow: 'lg' }}
>
Submit
</Button>
</Stack>
</Box>
</ChakraProvider>
);
}
// ColorModeToggle.tsx
import { useColorMode, IconButton } from '@chakra-ui/react';
import { SunIcon, MoonIcon } from '@chakra-ui/icons';
function ColorModeToggle() {
const { colorMode, toggleColorMode } = useColorMode();
return (
<IconButton
aria-label="Toggle color mode"
icon={colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
onClick={toggleColorMode}
/>
);
}
Example: shadcn/ui with Radix primitives (copy-paste approach)
// components/ui/button.tsx (copied from shadcn/ui)
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
ghost: "hover:bg-accent hover:text-accent-foreground"
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
// Usage
import { Button } from "@/components/ui/button";
function MyComponent() {
return (
<>
<Button variant="default">Default</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost" size="sm">Small Ghost</Button>
</>
);
}
MUI Pros: Material Design consistency, massive component library (100+), strong TypeScript
support, excellent documentation, MUI X for advanced components (DataGrid, Date Pickers).
Chakra UI Pros: Excellent DX with style props, built-in dark mode, smaller bundle, composable
design, fast setup. Great for startups and modern apps.
Styling Strategy Decision Matrix
| Use Case | Recommended Approach | Rationale |
|---|---|---|
| Rapid prototyping | Tailwind CSS + shadcn/ui | Fast development, no custom CSS, full control |
| Enterprise dashboard | MUI or Ant Design | Rich components, data tables, consistency |
| Design system from scratch | Radix UI + CSS Variables + Tokens | Full customization, brand consistency |
| Performance-critical | Tailwind CSS + Linaria | Zero-runtime CSS, optimal bundle size |
| Dynamic theming | CSS Variables + Chakra UI | Runtime theme switching, easy dark mode |