Color Management and Theme Systems
1. Color Palette Maps and Theme Variables
| Pattern | Syntax | Description | Use Case |
|---|---|---|---|
| Color Maps | $colors: (key: value) |
Organize colors in map structure | Brand color systems |
| Nested Palettes | $theme: (primary: (light: ..., dark: ...)) |
Multi-level color organization | Color variants |
| Semantic Naming | $color-primary, $color-success |
Purpose-based naming | Maintainable themes |
| Shade Generation | @function shade($color, $level) |
Auto-generate color variants | Consistent palettes |
| Color Tokens | $token-color-primary-500 |
Design token convention | Design system integration |
Example: Comprehensive color palette system
// Base color palette
$colors: (
primary: #1976d2,
secondary: #dc004e,
success: #4caf50,
warning: #ff9800,
error: #f44336,
info: #2196f3,
neutral: #9e9e9e
);
// Generate shade levels (50-900)
@function generate-shades($base-color) {
@return (
50: mix(white, $base-color, 90%),
100: mix(white, $base-color, 80%),
200: mix(white, $base-color, 60%),
300: mix(white, $base-color, 40%),
400: mix(white, $base-color, 20%),
500: $base-color,
600: mix(black, $base-color, 20%),
700: mix(black, $base-color, 40%),
800: mix(black, $base-color, 60%),
900: mix(black, $base-color, 80%)
);
}
// Complete theme with all shades
$theme-colors: ();
@each $name, $color in $colors {
$theme-colors: map-merge($theme-colors, (
$name: generate-shades($color)
));
}
// Access function
@function theme-color($color, $shade: 500) {
@return map-get(map-get($theme-colors, $color), $shade);
}
// Usage
.button-primary {
background: theme-color(primary);
&:hover { background: theme-color(primary, 600); }
&:active { background: theme-color(primary, 700); }
}
// Semantic color variables
$color-background: theme-color(neutral, 50);
$color-surface: white;
$color-text-primary: rgba(0, 0, 0, 0.87);
$color-text-secondary: rgba(0, 0, 0, 0.60);
$color-divider: rgba(0, 0, 0, 0.12);
Example: Advanced nested theme structure
// Multi-theme system
$themes: (
light: (
background: (
primary: #ffffff,
secondary: #f5f5f5,
elevated: #fafafa
),
text: (
primary: rgba(0, 0, 0, 0.87),
secondary: rgba(0, 0, 0, 0.60),
disabled: rgba(0, 0, 0, 0.38)
),
brand: (
primary: #1976d2,
secondary: #dc004e,
accent: #ff9800
)
),
dark: (
background: (
primary: #121212,
secondary: #1e1e1e,
elevated: #2c2c2c
),
text: (
primary: rgba(255, 255, 255, 0.87),
secondary: rgba(255, 255, 255, 0.60),
disabled: rgba(255, 255, 255, 0.38)
),
brand: (
primary: #90caf9,
secondary: #f48fb1,
accent: #ffb74d
)
)
);
// Deep map getter
@function theme-get($theme, $category, $variant) {
$theme-map: map-get($themes, $theme);
$category-map: map-get($theme-map, $category);
@return map-get($category-map, $variant);
}
2. Color Scheme Generation and Automation
| Technique | Implementation | Description | Output |
|---|---|---|---|
| Complementary | adjust-hue($color, 180deg) |
Opposite on color wheel | High contrast pairs |
| Analogous | adjust-hue($color, ±30deg) |
Adjacent colors | Harmonious schemes |
| Triadic | adjust-hue($color, 120deg) |
Evenly spaced colors | Vibrant palettes |
| Monochromatic | lighten/darken($color, %) |
Single hue variations | Cohesive design |
| Tint/Shade | mix(white/black, $color, %) |
Add white/black | Subtle variations |
| Tone | mix(gray, $color, %) |
Add gray | Muted colors |
Example: Automatic color scheme generators
// Complementary scheme
@function complementary-scheme($base) {
@return (
base: $base,
complement: adjust-hue($base, 180deg)
);
}
// Analogous scheme
@function analogous-scheme($base, $angle: 30deg) {
@return (
base: $base,
left: adjust-hue($base, -$angle),
right: adjust-hue($base, $angle)
);
}
// Triadic scheme
@function triadic-scheme($base) {
@return (
primary: $base,
secondary: adjust-hue($base, 120deg),
tertiary: adjust-hue($base, 240deg)
);
}
// Split complementary
@function split-complementary($base, $angle: 150deg) {
@return (
base: $base,
left: adjust-hue($base, $angle),
right: adjust-hue($base, -$angle)
);
}
// Monochromatic with tints and shades
@function monochromatic-scheme($base, $steps: 5) {
$scheme: ();
@for $i from 1 through $steps {
$lighter: lighten($base, $i * 10%);
$darker: darken($base, $i * 10%);
$scheme: map-merge($scheme, (
'light-#{$i}': $lighter,
'dark-#{$i}': $darker
));
}
@return map-merge($scheme, (base: $base));
}
// Usage
$brand: #1976d2;
$palette: triadic-scheme($brand);
.primary { color: map-get($palette, primary); }
.secondary { color: map-get($palette, secondary); }
.tertiary { color: map-get($palette, tertiary); }
Example: Material Design-style palette generator
@use 'sass:color';
@use 'sass:map';
@function material-palette($base-color) {
$palette: ();
// Define lightness levels
$levels: (
50: 95%,
100: 90%,
200: 80%,
300: 70%,
400: 60%,
500: 50%, // base
600: 40%,
700: 30%,
800: 20%,
900: 10%
);
@each $level, $lightness in $levels {
$adjusted: change-color($base-color, $lightness: $lightness);
$palette: map.merge($palette, ($level: $adjusted));
}
// Add accent colors (A100-A700)
$palette: map.merge($palette, (
'A100': lighten(saturate($base-color, 20%), 20%),
'A200': lighten(saturate($base-color, 20%), 10%),
'A400': saturate($base-color, 20%),
'A700': darken(saturate($base-color, 20%), 10%)
));
@return $palette;
}
// Generate complete color system
$primary: material-palette(#1976d2);
$accent: material-palette(#ff4081);
.button {
background: map.get($primary, 500);
&:hover { background: map.get($primary, 600); }
&.accent { background: map.get($accent, 'A200'); }
}
3. Dark Mode and Light Mode Implementation
| Approach | Method | Pros | Cons |
|---|---|---|---|
| CSS Variables | Change custom properties | Runtime switching, scoped | Browser support |
| Separate Files | Load different CSS | Simple, cacheable | Flash of wrong theme |
| Class Toggle | .dark-mode class |
Full control | CSS duplication |
| Media Query | prefers-color-scheme |
Respects OS setting | No manual override |
| Hybrid | Media query + class | Best of both | More complex |
Example: CSS custom properties approach (recommended)
// Define theme maps
$light-theme: (
bg-primary: #ffffff,
bg-secondary: #f5f5f5,
text-primary: #212121,
text-secondary: #757575,
border: #e0e0e0,
shadow: rgba(0, 0, 0, 0.1)
);
$dark-theme: (
bg-primary: #121212,
bg-secondary: #1e1e1e,
text-primary: #ffffff,
text-secondary: #b0b0b0,
border: #333333,
shadow: rgba(0, 0, 0, 0.5)
);
// Generate CSS variables mixin
@mixin theme-variables($theme) {
@each $name, $value in $theme {
--color-#{$name}: #{$value};
}
}
// Apply themes
:root {
@include theme-variables($light-theme);
color-scheme: light;
}
[data-theme="dark"] {
@include theme-variables($dark-theme);
color-scheme: dark;
}
// Respect system preference
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
@include theme-variables($dark-theme);
color-scheme: dark;
}
}
// Usage in components
.card {
background: var(--color-bg-primary);
color: var(--color-text-primary);
border: 1px solid var(--color-border);
box-shadow: 0 2px 4px var(--color-shadow);
}
Example: SCSS mixin-based theme system
// Theme configuration
$themes: (
light: (
background: #ffffff,
surface: #f5f5f5,
primary: #1976d2,
on-background: #000000,
on-surface: #212121,
on-primary: #ffffff
),
dark: (
background: #121212,
surface: #1e1e1e,
primary: #90caf9,
on-background: #ffffff,
on-surface: #e0e0e0,
on-primary: #000000
)
);
// Theme mixin
@mixin themed($property, $color-key) {
@each $theme-name, $theme-colors in $themes {
[data-theme="#{$theme-name}"] & {
#{$property}: map-get($theme-colors, $color-key);
}
}
}
// Multi-property theme mixin
@mixin theme-colors($map) {
@each $theme-name, $theme-colors in $themes {
[data-theme="#{$theme-name}"] & {
@each $property, $color-key in $map {
#{$property}: map-get($theme-colors, $color-key);
}
}
}
}
// Usage
.button {
@include themed(background-color, primary);
@include themed(color, on-primary);
&:hover {
@include themed(background-color, surface);
}
}
.card {
@include theme-colors((
background-color: surface,
color: on-surface,
border-color: primary
));
}
Example: Advanced dark mode with elevation
// Dark mode elevation system
@function dark-elevation($level) {
$opacity: 0.05 * $level;
@return rgba(255, 255, 255, $opacity);
}
// Elevation mixin for dark mode
@mixin elevation($level: 1) {
[data-theme="light"] & {
box-shadow: 0 #{$level * 2}px #{$level * 4}px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] & {
background: mix(white, #121212, $level * 5%);
box-shadow: 0 #{$level * 2}px #{$level * 4}px rgba(0, 0, 0, 0.5);
}
}
// Surface tint for Material Design 3
@mixin surface-tint($level: 1) {
[data-theme="light"] & {
background: white;
}
[data-theme="dark"] & {
$tint: mix(#90caf9, #121212, $level * 5%);
background: $tint;
}
}
.card {
@include elevation(2);
&.elevated {
@include elevation(8);
}
&.surface {
@include surface-tint(3);
}
}
4. Accessibility Color Contrast Functions
| Function | Purpose | WCAG Level | Ratio Required |
|---|---|---|---|
| contrast-ratio() | Calculate contrast ratio | - | Returns number |
| is-accessible() | Check WCAG compliance | AA/AAA | 4.5:1 / 7:1 |
| accessible-color() | Auto-adjust for contrast | AA | 4.5:1 |
| text-color() | Choose black/white text | AA | Based on background |
| Large Text | 18pt+ or 14pt+ bold | AA | 3:1 |
| UI Components | Interactive elements | AA | 3:1 |
Example: Contrast ratio calculation and validation
@use 'sass:math';
@use 'sass:color';
// Calculate relative luminance
@function luminance($color) {
$r: color.red($color) / 255;
$g: color.green($color) / 255;
$b: color.blue($color) / 255;
$r: if($r <= 0.03928, $r / 12.92, math.pow(($r + 0.055) / 1.055, 2.4));
$g: if($g <= 0.03928, $g / 12.92, math.pow(($g + 0.055) / 1.055, 2.4));
$b: if($b <= 0.03928, $b / 12.92, math.pow(($b + 0.055) / 1.055, 2.4));
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}
// Calculate contrast ratio
@function contrast-ratio($fg, $bg) {
$l1: luminance($fg);
$l2: luminance($bg);
$lighter: math.max($l1, $l2);
$darker: math.min($l1, $l2);
@return math.div($lighter + 0.05, $darker + 0.05);
}
// Check WCAG compliance
@function is-accessible($fg, $bg, $level: 'AA', $size: 'normal') {
$ratio: contrast-ratio($fg, $bg);
@if $level == 'AAA' {
@return if($size == 'large', $ratio >= 4.5, $ratio >= 7);
} @else {
@return if($size == 'large', $ratio >= 3, $ratio >= 4.5);
}
}
// Usage
$bg: #1976d2;
$text: #ffffff;
@debug contrast-ratio($text, $bg); // 4.53
@debug is-accessible($text, $bg); // true
@debug is-accessible($text, $bg, 'AAA'); // false
Example: Auto-adjust colors for accessibility
// Choose best text color (black or white)
@function text-color($bg, $light: #ffffff, $dark: #000000) {
$light-ratio: contrast-ratio($light, $bg);
$dark-ratio: contrast-ratio($dark, $bg);
@return if($light-ratio > $dark-ratio, $light, $dark);
}
// Ensure accessible color
@function accessible-color($fg, $bg, $target-ratio: 4.5) {
$ratio: contrast-ratio($fg, $bg);
@if $ratio >= $target-ratio {
@return $fg;
}
// Try darkening
$darkened: $fg;
@for $i from 1 through 20 {
$darkened: darken($darkened, 5%);
@if contrast-ratio($darkened, $bg) >= $target-ratio {
@return $darkened;
}
}
// Try lightening
$lightened: $fg;
@for $i from 1 through 20 {
$lightened: lighten($lightened, 5%);
@if contrast-ratio($lightened, $bg) >= $target-ratio {
@return $lightened;
}
}
// Fallback to black or white
@return text-color($bg);
}
// Mixin for accessible text
@mixin accessible-text($bg, $level: 'AA') {
$target: if($level == 'AAA', 7, 4.5);
color: accessible-color(inherit, $bg, $target);
}
// Usage
.button {
background: #ff9800;
@include accessible-text(#ff9800);
}
.badge {
$bg: #e3f2fd;
background: $bg;
color: text-color($bg);
}
Example: Accessibility validation mixin
// Validate and warn about contrast issues
@mixin validate-contrast($fg, $bg, $context: '') {
$ratio: contrast-ratio($fg, $bg);
@if $ratio < 3 {
@error '#{$context} Contrast ratio #{$ratio} fails WCAG (minimum 3:1)';
} @else if $ratio < 4.5 {
@warn '#{$context} Contrast ratio #{$ratio} passes for large text only';
} @else if $ratio < 7 {
@warn '#{$context} Contrast ratio #{$ratio} passes AA but not AAA';
}
}
// Auto-validate color usage
@mixin color-with-validation($property, $color, $bg, $context: '') {
@include validate-contrast($color, $bg, $context);
#{$property}: $color;
}
// Usage
.button-primary {
$bg: #1976d2;
background: $bg;
@include color-with-validation(color, #ffffff, $bg, 'Button primary');
}
// Generate accessible palette
@function accessible-palette($base, $bg: #ffffff) {
$palette: ();
@for $i from 1 through 9 {
$shade: darken($base, $i * 10%);
@if is-accessible($shade, $bg) {
$palette: append($palette, $shade);
}
}
@return $palette;
}
5. Dynamic Color Manipulation Functions
| Function | Syntax | Description | Use Case |
|---|---|---|---|
| adjust-hue() | adjust-hue($color, $degrees) |
Rotate hue on color wheel | Color variations |
| saturate() | saturate($color, $amount) |
Increase saturation | Make colors vibrant |
| desaturate() | desaturate($color, $amount) |
Decrease saturation | Muted colors |
| mix() | mix($color1, $color2, $weight) |
Blend two colors | Color transitions |
| scale-color() | scale-color($color, $red: 0%) |
Scale RGB/HSL values | Precise adjustments |
| change-color() | change-color($color, $hue: 0) |
Set specific channel | Direct manipulation |
| complement() | complement($color) |
Opposite hue (180°) | Complementary colors |
| invert() | invert($color, $weight) |
Invert RGB values | Negative colors |
| grayscale() | grayscale($color) |
Remove saturation | Monochrome |
Example: Advanced color manipulation utilities
@use 'sass:color';
// Smart color adjustment based on lightness
@function smart-scale($color, $amount) {
$lightness: color.lightness($color);
@if $lightness > 70% {
@return darken($color, $amount);
} @else if $lightness < 30% {
@return lighten($color, $amount);
} @else {
@return saturate($color, $amount);
}
}
// Create tint (mix with white)
@function tint($color, $percentage) {
@return mix(white, $color, $percentage);
}
// Create shade (mix with black)
@function shade($color, $percentage) {
@return mix(black, $color, $percentage);
}
// Create tone (mix with gray)
@function tone($color, $percentage) {
@return mix(gray, $color, $percentage);
}
// Alpha transparency
@function alpha($color, $opacity) {
@return rgba($color, $opacity);
}
// Multi-step color transformation
@function transform-color($color, $transforms...) {
$result: $color;
@each $transform in $transforms {
$fn: nth($transform, 1);
$args: if(length($transform) > 1, nth($transform, 2), null);
@if $fn == 'lighten' {
$result: lighten($result, $args);
} @else if $fn == 'darken' {
$result: darken($result, $args);
} @else if $fn == 'saturate' {
$result: saturate($result, $args);
} @else if $fn == 'adjust-hue' {
$result: adjust-hue($result, $args);
}
}
@return $result;
}
// Usage
$base: #1976d2;
$tinted: tint($base, 20%);
$shaded: shade($base, 20%);
$toned: tone($base, 30%);
.button {
background: transform-color($base,
(darken, 10%),
(saturate, 20%)
);
}
Example: Color harmony and relationship functions
// Get complementary color
@function complementary($color) {
@return adjust-hue($color, 180deg);
}
// Get analogous colors
@function analogous($color, $angle: 30deg) {
@return (
adjust-hue($color, -$angle),
$color,
adjust-hue($color, $angle)
);
}
// Get triadic colors
@function triadic($color) {
@return (
$color,
adjust-hue($color, 120deg),
adjust-hue($color, 240deg)
);
}
// Get split-complementary
@function split-complementary($color, $angle: 150deg) {
@return (
$color,
adjust-hue($color, $angle),
adjust-hue($color, -$angle)
);
}
// Get tetradic (rectangle)
@function tetradic($color, $offset: 60deg) {
@return (
$color,
adjust-hue($color, $offset),
adjust-hue($color, 180deg),
adjust-hue($color, 180deg + $offset)
);
}
// Color temperature shift
@function warm($color, $amount: 10%) {
@return adjust-hue($color, -$amount * 3.6deg);
}
@function cool($color, $amount: 10%) {
@return adjust-hue($color, $amount * 3.6deg);
}
// Perceptual lightness adjustment
@function perceptual-lighten($color, $amount) {
@return scale-color($color, $lightness: $amount);
}
@function perceptual-darken($color, $amount) {
@return scale-color($color, $lightness: -$amount);
}
Example: State color generation
// Generate all button states from base color
@function button-states($base) {
@return (
default: $base,
hover: darken($base, 8%),
active: darken($base, 12%),
focus: $base,
disabled: desaturate(lighten($base, 20%), 40%),
loading: desaturate($base, 20%)
);
}
// Generate input states
@function input-states($base-border: #ccc) {
@return (
default: $base-border,
hover: darken($base-border, 10%),
focus: adjust-hue($base-border, 200deg),
error: #f44336,
success: #4caf50,
disabled: lighten($base-border, 10%)
);
}
// Mixin to apply all states
@mixin button-colors($base) {
$states: button-states($base);
background: map-get($states, default);
&:hover:not(:disabled) {
background: map-get($states, hover);
}
&:active:not(:disabled) {
background: map-get($states, active);
}
&:focus-visible {
outline: 2px solid map-get($states, focus);
outline-offset: 2px;
}
&:disabled {
background: map-get($states, disabled);
cursor: not-allowed;
opacity: 0.6;
}
}
// Usage
.btn-primary {
@include button-colors(#1976d2);
}
.btn-success {
@include button-colors(#4caf50);
}
6. Color Token Systems and Design System Integration
| Pattern | Structure | Description | Example |
|---|---|---|---|
| Core Tokens | Base color values | Primitive colors | $blue-500: #1976d2 |
| Semantic Tokens | Purpose-based names | Functional colors | $color-primary: $blue-500 |
| Component Tokens | Component-specific | Scoped colors | $button-bg: $color-primary |
| Token Aliasing | Reference other tokens | Single source of truth | $link: $color-primary |
| Token Scale | Numbered variations | Consistent gradations | 50, 100, 200...900 |
| Theme Tokens | Mode-specific values | Light/dark variants | $bg-light, $bg-dark |
Example: Three-tier token system
// ===== TIER 1: Core/Primitive Tokens =====
// These are the base color values
$blue-50: #e3f2fd;
$blue-100: #bbdefb;
$blue-200: #90caf9;
$blue-300: #64b5f6;
$blue-400: #42a5f5;
$blue-500: #2196f3;
$blue-600: #1e88e5;
$blue-700: #1976d2;
$blue-800: #1565c0;
$blue-900: #0d47a1;
$gray-50: #fafafa;
$gray-100: #f5f5f5;
$gray-200: #eeeeee;
$gray-300: #e0e0e0;
// ... more grays
$red-500: #f44336;
$green-500: #4caf50;
$orange-500: #ff9800;
// ===== TIER 2: Semantic Tokens =====
// Purpose-based tokens
$color-primary: $blue-700;
$color-primary-light: $blue-500;
$color-primary-dark: $blue-900;
$color-secondary: $red-500;
$color-success: $green-500;
$color-warning: $orange-500;
$color-error: $red-500;
$color-info: $blue-500;
// Background tokens
$color-background: $gray-50;
$color-surface: #ffffff;
$color-overlay: rgba(0, 0, 0, 0.5);
// Text tokens
$color-text-primary: rgba(0, 0, 0, 0.87);
$color-text-secondary: rgba(0, 0, 0, 0.60);
$color-text-disabled: rgba(0, 0, 0, 0.38);
$color-text-hint: rgba(0, 0, 0, 0.38);
// Border tokens
$color-border: $gray-300;
$color-divider: rgba(0, 0, 0, 0.12);
// ===== TIER 3: Component Tokens =====
// Component-specific tokens
$button-bg-primary: $color-primary;
$button-text-primary: #ffffff;
$button-bg-secondary: $color-secondary;
$button-border-radius: 4px;
$input-bg: $color-surface;
$input-border: $color-border;
$input-text: $color-text-primary;
$input-placeholder: $color-text-secondary;
$card-bg: $color-surface;
$card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
$link-color: $color-primary;
$link-hover: $color-primary-dark;
Example: Design system integration with Style Dictionary
// tokens.scss - Source tokens in SCSS format
$tokens: (
color: (
core: (
blue: (
50: #e3f2fd,
100: #bbdefb,
500: #2196f3,
900: #0d47a1
),
gray: (
50: #fafafa,
500: #9e9e9e,
900: #212121
)
),
semantic: (
primary: (
default: '{color.core.blue.500}',
light: '{color.core.blue.50}',
dark: '{color.core.blue.900}'
),
text: (
primary: '{color.core.gray.900}',
secondary: '{color.core.gray.500}'
)
),
component: (
button: (
background: '{color.semantic.primary.default}',
text: '#ffffff',
hover: '{color.semantic.primary.dark}'
),
input: (
background: '#ffffff',
border: '{color.core.gray.500}',
text: '{color.semantic.text.primary}'
)
)
),
spacing: (
xs: 4px,
sm: 8px,
md: 16px,
lg: 24px,
xl: 32px
)
);
// Token resolver function
@function resolve-token($path) {
// Parse token reference like '{color.core.blue.500}'
@if str-index($path, '{') {
$clean: str-slice($path, 2, -2); // Remove { }
$keys: split-string($clean, '.');
$value: $tokens;
@each $key in $keys {
$value: map-get($value, $key);
}
@return $value;
}
@return $path;
}
// Generate CSS custom properties
@mixin generate-css-vars($map, $prefix: '') {
@each $key, $value in $map {
@if type-of($value) == 'map' {
@include generate-css-vars($value, '#{$prefix}#{$key}-');
} @else {
--#{$prefix}#{$key}: #{resolve-token($value)};
}
}
}
:root {
@include generate-css-vars(map-get($tokens, color), 'color-');
@include generate-css-vars(map-get($tokens, spacing), 'spacing-');
}
Example: Multi-brand token system
// Brand token configuration
$brands: (
acme: (
primary: #1976d2,
secondary: #dc004e,
logo: url('/brands/acme-logo.svg')
),
globex: (
primary: #4caf50,
secondary: #ff9800,
logo: url('/brands/globex-logo.svg')
),
initech: (
primary: #9c27b0,
secondary: #00bcd4,
logo: url('/brands/initech-logo.svg')
)
);
// Generate brand-specific tokens
@each $brand-name, $brand-config in $brands {
[data-brand="#{$brand-name}"] {
--brand-primary: #{map-get($brand-config, primary)};
--brand-secondary: #{map-get($brand-config, secondary)};
--brand-logo: #{map-get($brand-config, logo)};
// Generate palette from primary
--brand-primary-light: #{lighten(map-get($brand-config, primary), 20%)};
--brand-primary-dark: #{darken(map-get($brand-config, primary), 20%)};
// Generate contrast colors
--brand-on-primary: #{text-color(map-get($brand-config, primary))};
--brand-on-secondary: #{text-color(map-get($brand-config, secondary))};
}
}
// Component usage
.header {
background: var(--brand-primary);
color: var(--brand-on-primary);
&__logo {
background-image: var(--brand-logo);
}
}
.button-primary {
background: var(--brand-primary);
color: var(--brand-on-primary);
&:hover {
background: var(--brand-primary-dark);
}
}
Example: Token documentation generator
// Generate color documentation
@mixin document-colors($color-map, $name: '') {
@if type-of($color-map) == 'color' {
// Generate a swatch
.color-swatch-#{$name} {
&::before {
content: '#{$name}: #{$color-map}';
display: inline-block;
width: 100px;
height: 40px;
background: $color-map;
margin-right: 10px;
border: 1px solid #ccc;
}
}
} @else if type-of($color-map) == 'map' {
@each $key, $value in $color-map {
$full-name: if($name == '', $key, '#{$name}-#{$key}');
@include document-colors($value, $full-name);
}
}
}
// Generate documentation
@include document-colors($tokens);
// Export as JSON for documentation tools
@function tokens-to-json($map, $indent: 0) {
$json: '{\n';
$i: 0;
@each $key, $value in $map {
$i: $i + 1;
$spacing: '';
@for $s from 1 through ($indent + 1) {
$spacing: $spacing + ' ';
}
$json: $json + $spacing + '"#{$key}": ';
@if type-of($value) == 'map' {
$json: $json + tokens-to-json($value, $indent + 1);
} @else {
$json: $json + '"#{$value}"';
}
@if $i < length($map) {
$json: $json + ',';
}
$json: $json + '\n';
}
$spacing: '';
@for $s from 1 through $indent {
$spacing: $spacing + ' ';
}
$json: $json + $spacing + '}';
@return $json;
}
Color Management Best Practices
- Three-tier tokens: Core → Semantic → Component for scalability
- CSS variables: Enable runtime theme switching and scoping
- Accessibility first: Always validate contrast ratios (4.5:1 minimum)
- Dark mode: Use hybrid approach (media query + manual toggle)
- Semantic naming: Use purpose-based names, not color names
- Automated generation: Generate palettes and states programmatically
- Design tokens: Single source of truth for multi-platform consistency
- Contrast functions: Validate and auto-adjust colors for WCAG compliance
Note: Modern Sass modules (
sass:color) provide more precise color manipulation
with better support for different color spaces. Use @use 'sass:color' for
future-proof implementations.