Create semantic color tokens, not just raw colors.
Create semantic color tokens, not just raw colors. This enables theme switching and maintains consistency.
:root {
/* Background colors */
--color-background: hsl(0 0% 100%);
--color-foreground: hsl(222.2 84% 4.9%);
/* Primary brand color */
--color-primary: hsl(222.2 47.4% 11.2%);
--color-primary-hover: hsl(222.2 47.4% 16%);
--color-primary-foreground: hsl(210 40% 98%);
/* Secondary color */
--color-secondary: hsl(210 40% 96.1%);
--color-secondary-hover: hsl(210 40% 92%);
--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
/* Accent color */
--color-accent: hsl(210 40% 96.1%);
--color-accent-hover: hsl(210 40% 92%);
--color-accent-foreground: hsl(222.2 47.4% 11.2%);
/* Muted/subtle elements */
--color-muted: hsl(210 40% 96.1%);
--color-muted-foreground: hsl(215.4 16.3% 46.9%);
/* Borders and dividers */
--color-border: hsl(214.3 31.8% 91.4%);
/* Status colors */
--color-error: hsl(0 84.2% 60.2%);
--color-error-foreground: hsl(0 0% 100%);
--color-warning: hsl(38 92% 50%);
--color-warning-foreground: hsl(0 0% 100%);
--color-success: hsl(142 76% 36%);
--color-success-foreground: hsl(0 0% 100%);
--color-info: hsl(199 89% 48%);
--color-info-foreground: hsl(0 0% 100%);
}
Dark mode is not just inverted colors - it requires different design considerations.
@media (prefers-color-scheme: dark) {
:root {
--color-background: hsl(222.2 84% 4.9%);
--color-foreground: hsl(210 40% 98%);
--color-primary: hsl(210 40% 98%);
--color-primary-hover: hsl(210 40% 95%);
--color-primary-foreground: hsl(222.2 47.4% 11.2%);
--color-secondary: hsl(217.2 32.6% 17.5%);
--color-secondary-hover: hsl(217.2 32.6% 22%);
--color-secondary-foreground: hsl(210 40% 98%);
--color-muted: hsl(217.2 32.6% 17.5%);
--color-muted-foreground: hsl(215 20.2% 65.1%);
--color-border: hsl(217.2 32.6% 17.5%);
/* Status colors - adjusted for dark mode */
--color-error: hsl(0 62.8% 30.6%);
--color-warning: hsl(38 92% 40%);
--color-success: hsl(142 70% 25%);
--color-info: hsl(199 89% 35%);
}
}
Dark Mode Guidelines:
60-30-10 Rule:
Generate Scales:
Starting with a primary color, generate tints (add white) and shades (add black) in 10% increments:
// Example: Generate color scale from base
const baseColor = 'hsl(222, 47%, 11%)';
const scale = {
50: lighten(baseColor, 0.5), // lightest
100: lighten(baseColor, 0.4),
200: lighten(baseColor, 0.3),
300: lighten(baseColor, 0.2),
400: lighten(baseColor, 0.1),
500: baseColor, // base
600: darken(baseColor, 0.1),
700: darken(baseColor, 0.2),
800: darken(baseColor, 0.3),
900: darken(baseColor, 0.4), // darkest
};
WCAG AA compliance:
Test with:
/* Good contrast examples */
.good-text {
background: #ffffff;
color: #222222; /* 16.1:1 ratio */
}
.good-button {
background: #0066cc;
color: #ffffff; /* 7.5:1 ratio */
}
/* Poor contrast - avoid */
.poor-text {
background: #ffffff;
color: #999999; /* 2.8:1 - fails WCAG AA */
}
Use subtle shadows to create depth, not heavy drop-shadows.
:root {
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}
/* Usage */
.card {
box-shadow: var(--shadow-md);
}
.modal {
box-shadow: var(--shadow-xl);
}
Dark Mode Shadows:
@media (prefers-color-scheme: dark) {
:root {
/* Stronger shadows needed in dark mode for depth */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.6);
}
}
Use consistent spacing that creates visual rhythm. Base unit of 8px.
:root {
--space-0: 0;
--space-0-5: 0.125rem; /* 2px */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
--space-32: 8rem; /* 128px */
}
Spacing creates relationships:
/* Example: Card component */
.card {
padding: var(--space-6); /* 24px internal padding */
margin-bottom: var(--space-4); /* 16px between cards */
gap: var(--space-4); /* 16px between card elements */
}
.card-title {
margin-bottom: var(--space-2); /* 8px below title */
}
.card-section {
margin-top: var(--space-6); /* 24px between sections */
}
More is better:
Practical application:
/* Good: Clear grouping with space */
.form-group {
display: flex;
flex-direction: column;
gap: var(--space-2); /* 8px between label and input */
margin-bottom: var(--space-6); /* 24px between form groups */
}
/* Good: Section separation */
.section {
margin-bottom: var(--space-12); /* 48px between major sections */
}
:root {
/* ===== COLORS ===== */
/* Light mode */
--color-background: #ffffff;
--color-foreground: #0a0a0a;
--color-primary: #0070f3;
--color-primary-hover: #0051cc;
--color-border: #eaeaea;
--color-error: #e00;
--color-success: #0070f3;
--color-warning: #f5a623;
/* ===== SPACING ===== */
--space-2: 0.5rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
/* ===== ELEVATION ===== */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: #000000;
--color-foreground: #e4e4e4;
--color-primary: #3291ff;
--color-primary-hover: #0070f3;
--color-border: #333333;
/* Stronger shadows for dark mode */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5);
}
}
/* Apply to document */
body {
background: var(--color-background);
color: var(--color-foreground);
}
Palette generators:
Contrast checkers:
Dark mode tools:
| Element | Light Mode | Dark Mode | Usage | |---------|-----------|-----------|--------| | Background | #ffffff | #000000 | Main surface | | Foreground | #0a0a0a | #e4e4e4 | Primary text | | Primary | #0070f3 | #3291ff | Brand color, CTAs | | Border | #eaeaea | #333333 | Dividers, outlines | | Error | #e00 | #ff3333 | Error states | | Success | #0070f3 | #3291ff | Success states | | Card padding | 24px (--space-6) | 24px | Internal spacing | | Section gap | 48px (--space-12) | 48px | Between sections |