SASS/SCSS

Color Management & Theming

20 min Lesson 20 of 30

Introduction to Color Management in SASS

Effective color management is crucial for creating consistent, maintainable, and accessible design systems. SASS provides powerful tools for defining color palettes, generating variations, and implementing complex theming systems. A well-structured color system makes your CSS easier to maintain, ensures design consistency, and simplifies implementing features like dark mode.

Benefits of Systematic Color Management

  • Consistency: Single source of truth for all colors
  • Maintainability: Change the entire color scheme by updating base values
  • Scalability: Easy to add new colors or variations
  • Theming: Simple implementation of light/dark modes or brand themes
  • Accessibility: Systematic approach to contrast ratios and WCAG compliance

Defining Color Palettes with Variables

Simple Variable Approach

Start with basic color variables for your design system:

Basic Color Variables

// Brand colors
$color-primary: #3498db;
$color-secondary: #2ecc71;
$color-accent: #e74c3c;

// Neutral colors
$color-white: #ffffff;
$color-black: #000000;
$color-gray-light: #ecf0f1;
$color-gray: #95a5a6;
$color-gray-dark: #34495e;

// Semantic colors
$color-success: #27ae60;
$color-warning: #f39c12;
$color-danger: #e74c3c;
$color-info: #3498db;

// Text colors
$color-text-primary: #2c3e50;
$color-text-secondary: #7f8c8d;
$color-text-muted: #bdc3c7;

// Usage
.button-primary {
  background-color: $color-primary;
  color: $color-white;
}

Structured Color System with Maps

Use maps for more organized and flexible color management:

Map-Based Color System

$colors: (
  // Brand
  primary: #3498db,
  secondary: #2ecc71,
  accent: #9b59b6,

  // Semantic
  success: #27ae60,
  warning: #f39c12,
  danger: #e74c3c,
  info: #3498db,

  // Neutral
  white: #ffffff,
  black: #000000,
  gray-100: #f8f9fa,
  gray-200: #e9ecef,
  gray-300: #dee2e6,
  gray-400: #ced4da,
  gray-500: #adb5bd,
  gray-600: #6c757d,
  gray-700: #495057,
  gray-800: #343a40,
  gray-900: #212529
);

// Color getter function
@function color($name) {
  @if map-has-key($colors, $name) {
    @return map-get($colors, $name);
  }
  @warn "Color '#{$name}' not found in $colors map.";
  @return null;
}

// Usage
.alert-success {
  background-color: color(success);
  color: color(white);
}

Generating Color Scales

Using Built-in Color Functions

SASS provides functions to generate lighter and darker variations:

Color Variation Functions

$primary: #3498db;

// Lighten - makes color lighter
$primary-light: lighten($primary, 10%);    // Lighter by 10%
$primary-lighter: lighten($primary, 20%);  // Lighter by 20%

// Darken - makes color darker
$primary-dark: darken($primary, 10%);      // Darker by 10%
$primary-darker: darken($primary, 20%);    // Darker by 20%

// Saturate - increase color intensity
$primary-saturated: saturate($primary, 20%);

// Desaturate - decrease color intensity
$primary-muted: desaturate($primary, 20%);

// Mix - blend two colors
$purple: mix($primary, #e74c3c, 50%);      // 50% blend

// Adjust - fine-tune specific properties
$adjusted: adjust-color($primary,
  $red: 10,
  $green: -5,
  $lightness: 5%
);
Warning: lighten() and darken() adjust lightness in HSL color space, which can sometimes produce unexpected results. Consider using scale-color() or modern color manipulation for more predictable outcomes.

Creating Systematic Shade/Tint Functions

Build custom functions for consistent color scales:

Shade and Tint Functions

// Tint: mix color with white
@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

// Shade: mix color with black
@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

// Generate a complete scale
$primary: #3498db;

$primary-100: tint($primary, 80%);  // Very light
$primary-200: tint($primary, 60%);
$primary-300: tint($primary, 40%);
$primary-400: tint($primary, 20%);
$primary-500: $primary;             // Base
$primary-600: shade($primary, 20%);
$primary-700: shade($primary, 40%);
$primary-800: shade($primary, 60%);
$primary-900: shade($primary, 80%); // Very dark

// Usage
.button {
  background: $primary-500;

  &:hover {
    background: $primary-600;
  }

  &:active {
    background: $primary-700;
  }
}

Building a Color System with Maps

Nested Color Maps

Create a comprehensive color system with variations:

Complete Color Scale System

@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

// Generate color scale from base color
@function generate-scale($base-color) {
  @return (
    50: tint($base-color, 90%),
    100: tint($base-color, 80%),
    200: tint($base-color, 60%),
    300: tint($base-color, 40%),
    400: tint($base-color, 20%),
    500: $base-color,
    600: shade($base-color, 20%),
    700: shade($base-color, 40%),
    800: shade($base-color, 60%),
    900: shade($base-color, 80%)
  );
}

// Color palette with scales
$color-system: (
  primary: generate-scale(#3498db),
  secondary: generate-scale(#2ecc71),
  danger: generate-scale(#e74c3c),
  warning: generate-scale(#f39c12),
  gray: (
    50: #f8f9fa,
    100: #f1f3f5,
    200: #e9ecef,
    300: #dee2e6,
    400: #ced4da,
    500: #adb5bd,
    600: #6c757d,
    700: #495057,
    800: #343a40,
    900: #212529
  )
);

// Getter function
@function color($palette, $shade: 500) {
  $color-palette: map-get($color-system, $palette);
  @if $color-palette {
    $color-value: map-get($color-palette, $shade);
    @if $color-value {
      @return $color-value;
    }
  }
  @warn "Color #{$palette}-#{$shade} not found.";
  @return null;
}

// Usage
.button-primary {
  background: color(primary, 500);
  border-color: color(primary, 600);

  &:hover {
    background: color(primary, 600);
  }
}

.alert-danger {
  background: color(danger, 100);
  border-left: 4px solid color(danger, 500);
  color: color(danger, 900);
}

Contrast and Accessibility

Contrast Ratio Calculation

Ensure colors meet WCAG accessibility standards:

Contrast Ratio Functions

@use "sass:math";

// Calculate relative luminance
@function luminance($color) {
  $red: math.div(red($color), 255);
  $green: math.div(green($color), 255);
  $blue: math.div(blue($color), 255);

  $r: if($red <= 0.03928, math.div($red, 12.92), math.pow(math.div($red + 0.055, 1.055), 2.4));
  $g: if($green <= 0.03928, math.div($green, 12.92), math.pow(math.div($green + 0.055, 1.055), 2.4));
  $b: if($blue <= 0.03928, math.div($blue, 12.92), math.pow(math.div($blue + 0.055, 1.055), 2.4));

  @return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}

// Calculate contrast ratio
@function contrast-ratio($color1, $color2) {
  $l1: luminance($color1);
  $l2: luminance($color2);

  @if $l1 > $l2 {
    @return math.div($l1 + 0.05, $l2 + 0.05);
  } @else {
    @return math.div($l2 + 0.05, $l1 + 0.05);
  }
}

// Choose contrasting text color
@function contrasting-color($background, $light: white, $dark: black) {
  $light-contrast: contrast-ratio($background, $light);
  $dark-contrast: contrast-ratio($background, $dark);

  @if $light-contrast > $dark-contrast {
    @return $light;
  } @else {
    @return $dark;
  }
}

// Usage
.button {
  $bg-color: #3498db;
  background-color: $bg-color;
  color: contrasting-color($bg-color);
}

Dark Mode Implementation

CSS Custom Properties Approach

The most flexible approach uses CSS custom properties:

Dark Mode with CSS Variables

// Define theme colors
$themes: (
  light: (
    bg-primary: #ffffff,
    bg-secondary: #f8f9fa,
    text-primary: #212529,
    text-secondary: #6c757d,
    border: #dee2e6,
    primary: #3498db,
    success: #27ae60,
    danger: #e74c3c
  ),
  dark: (
    bg-primary: #1a1a1a,
    bg-secondary: #2d2d2d,
    text-primary: #e9ecef,
    text-secondary: #adb5bd,
    border: #495057,
    primary: #5dade2,
    success: #2ecc71,
    danger: #ec7063
  )
);

// Generate CSS custom properties
:root {
  @each $name, $value in map-get($themes, light) {
    --color-#{$name}: #{$value};
  }
}

[data-theme="dark"] {
  @each $name, $value in map-get($themes, dark) {
    --color-#{$name}: #{$value};
  }
}

// Usage with CSS variables
.card {
  background: var(--color-bg-primary);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
}

.button-primary {
  background: var(--color-primary);
  color: var(--color-bg-primary);
}

Alternative: prefers-color-scheme

Automatically respond to system preferences:

System Preference Detection

// Light mode (default)
:root {
  --color-bg: #ffffff;
  --color-text: #212529;
  --color-primary: #3498db;
}

// Dark mode via system preference
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #1a1a1a;
    --color-text: #e9ecef;
    --color-primary: #5dade2;
  }
}

// Allow manual override
[data-theme="light"] {
  --color-bg: #ffffff;
  --color-text: #212529;
  --color-primary: #3498db;
}

[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #e9ecef;
  --color-primary: #5dade2;
}

Building a Complete Theme System

Multi-Theme Architecture

Flexible Theme System

// Theme definitions
$themes: (
  default: (
    colors: (
      primary: #3498db,
      secondary: #2ecc71,
      background: #ffffff,
      surface: #f8f9fa,
      text: #212529,
      text-secondary: #6c757d
    ),
    spacing: (
      unit: 8px
    ),
    typography: (
      font-family: "Inter, sans-serif",
      font-size-base: 16px
    )
  ),
  dark: (
    colors: (
      primary: #5dade2,
      secondary: #58d68d,
      background: #1a1a1a,
      surface: #2d2d2d,
      text: #e9ecef,
      text-secondary: #adb5bd
    ),
    spacing: (
      unit: 8px
    ),
    typography: (
      font-family: "Inter, sans-serif",
      font-size-base: 16px
    )
  ),
  high-contrast: (
    colors: (
      primary: #0066cc,
      secondary: #00aa00,
      background: #000000,
      surface: #1a1a1a,
      text: #ffffff,
      text-secondary: #cccccc
    ),
    spacing: (
      unit: 8px
    ),
    typography: (
      font-family: "Arial, sans-serif",
      font-size-base: 18px
    )
  )
);

// Theme mixin
@mixin theme($theme-name) {
  $theme: map-get($themes, $theme-name);

  @if $theme {
    $colors: map-get($theme, colors);

    @each $name, $value in $colors {
      --color-#{$name}: #{$value};
    }

    $spacing: map-get($theme, spacing);
    @each $name, $value in $spacing {
      --spacing-#{$name}: #{$value};
    }

    $typography: map-get($theme, typography);
    @each $name, $value in $typography {
      --typography-#{$name}: #{$value};
    }
  }
}

// Apply themes
:root {
  @include theme(default);
}

[data-theme="dark"] {
  @include theme(dark);
}

[data-theme="high-contrast"] {
  @include theme(high-contrast);
}

// Component usage
.component {
  background: var(--color-surface);
  color: var(--color-text);
  padding: calc(var(--spacing-unit) * 2);
  font-family: var(--typography-font-family);
}

Theme Toggle Implementation

JavaScript Integration

Theme Switching Logic

// JavaScript for theme switching
const ThemeManager = {
  init() {
    // Check saved preference
    const savedTheme = localStorage.getItem('theme') ||
      (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');

    this.setTheme(savedTheme);

    // Listen for system preference changes
    window.matchMedia('(prefers-color-scheme: dark)')
      .addEventListener('change', (e) => {
        if (!localStorage.getItem('theme')) {
          this.setTheme(e.matches ? 'dark' : 'light');
        }
      });
  },

  setTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  },

  toggle() {
    const current = document.documentElement.getAttribute('data-theme');
    const next = current === 'dark' ? 'light' : 'dark';
    this.setTheme(next);
  }
};

// Initialize
ThemeManager.init();

SASS for Theme Toggle Button

Theme Toggle Styles

.theme-toggle {
  position: relative;
  width: 60px;
  height: 30px;
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: 15px;
  cursor: pointer;
  transition: all 0.3s ease;

  &::before {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    background: var(--color-primary);
    border-radius: 50%;
    transition: transform 0.3s ease;
  }

  &:hover {
    border-color: var(--color-primary);
  }

  [data-theme="dark"] & {
    &::before {
      transform: translateX(30px);
    }
  }
}

Exercise 1: Brand Color System

Create a complete brand color system:

  1. Define 3 brand colors (primary, secondary, accent)
  2. Generate 10-shade scales (50-900) for each brand color
  3. Create semantic color mappings (success, warning, danger, info)
  4. Generate utility classes for backgrounds and text colors
  5. Include hover and active state variations
  6. Add accessible contrast checking for text colors

Exercise 2: Dark Mode Theme

Implement a complete dark mode system:

  1. Create light and dark theme color maps
  2. Generate CSS custom properties for both themes
  3. Build components that use CSS variables
  4. Add transition effects when switching themes
  5. Support system preference detection
  6. Include local storage persistence

Exercise 3: Multi-Brand Theming

Build a system that supports multiple brand themes:

  1. Define 3 complete brand themes (Brand A, B, C)
  2. Each theme includes: colors, typography, spacing, borders
  3. Create a theme mixin that applies all theme properties
  4. Generate CSS for each theme with data attributes
  5. Build reusable components that adapt to any theme
  6. Add theme preview/switcher functionality

Best Practices

Color Management Guidelines

  • Limit your palette: Use 3-5 brand colors plus neutrals
  • Create systematic scales: Use consistent shade/tint steps
  • Consider accessibility: Ensure adequate contrast ratios (WCAG AA: 4.5:1 for text)
  • Use semantic names: Prefer "primary" over "blue", "danger" over "red"
  • Test in context: Colors look different against different backgrounds
  • Document your system: Create a color style guide
  • Use CSS variables for theming: Enables runtime theme switching
  • Support dark mode: Modern users expect it

Summary

In this lesson, you mastered:

  • Defining organized color palettes with variables and maps
  • Generating color scales using shade/tint functions
  • Building systematic color systems with nested maps
  • Calculating contrast ratios for accessibility
  • Creating contrasting text color functions
  • Implementing dark mode with CSS custom properties
  • Building flexible multi-theme systems
  • Theme switching with data attributes and JavaScript
  • Best practices for maintainable color management

Effective color management transforms your CSS from a collection of arbitrary color values into a systematic, maintainable design language. By leveraging SASS's powerful features—maps, functions, and mixins—combined with modern CSS custom properties, you can create sophisticated theming systems that are both developer-friendly and user-friendly. A well-designed color system is the foundation of consistent, accessible, and beautiful interfaces.