SCSS Maps for Configuration

Introduction to SCSS Maps

SCSS maps are key-value pairs that allow you to organize related values and access them by their keys. They're similar to objects in JavaScript or dictionaries in Python.

Why Use Maps? Maps provide a structured way to store and retrieve related data, making your SCSS more maintainable, configurable, and easier to update. They're especially useful for theme configuration, responsive breakpoints, and design tokens.

Basic Map Syntax


// Basic map structure
$map-name: (
  'key1': value1,
  'key2': value2,
  'key3': value3
);

// Accessing map values
.element {
  property: map-get($map-name, 'key1');
}

Map Functions in SCSS

Function Description Example
map-get($map, $key) Returns the value for a given key in a map map-get($colors, 'primary')
map-has-key($map, $key) Checks if a map contains a specific key map-has-key($breakpoints, 'md')
map-keys($map) Returns a list of all keys in a map map-keys($spacing)
map-values($map) Returns a list of all values in a map map-values($z-indexes)
map-merge($map1, $map2) Merges two maps together map-merge($default-config, $custom-config)
map-remove($map, $keys...) Returns a new map with specified keys removed map-remove($theme, 'danger', 'warning')

Using Map Functions


// Define a colors map
$colors: (
  'primary': #0066cc,
  'secondary': #6c757d,
  'success': #28a745,
  'danger': #dc3545
);

// Using map-get
.button-primary {
  background-color: map-get($colors, 'primary');
}

// Using map-has-key
@if map-has-key($colors, 'warning') {
  .alert-warning {
    background-color: map-get($colors, 'warning');
  }
} @else {
  // Fallback if key doesn't exist
  .alert-warning {
    background-color: #ffc107;
  }
}

// Using map-merge to extend a map
$additional-colors: (
  'warning': #ffc107,
  'info': #17a2b8
);

$all-colors: map-merge($colors, $additional-colors);

Common Use Cases for SCSS Maps

1. Color Systems

Organize colors and create consistent palettes:


// Define brand colors
$colors: (
  'primary': (
    'base': #0066cc,
    'light': #4d94ff,
    'dark': #004c99
  ),
  'secondary': (
    'base': #6c757d,
    'light': #868e96,
    'dark': #495057
  ),
  'neutral': (
    'white': #ffffff,
    '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,
    'black': #000000
  )
);

// Function to access nested color values
@function color($color-name, $color-variant: 'base') {
  $color: map-get($colors, $color-name);
  
  @if $color != null {
    @if type-of($color) == 'map' {
      @return map-get($color, $color-variant);
    } @else {
      @return $color;
    }
  }
  
  @warn "Color `#{$color-name} - #{$color-variant}` not found.";
  @return null;
}

// Usage
.button {
  background-color: color('primary');
  color: color('neutral', 'white');
  border: 1px solid color('primary', 'dark');
  
  &:hover {
    background-color: color('primary', 'dark');
  }
}

.text-muted {
  color: color('neutral', 'gray-600');
}

2. Responsive Breakpoints

Manage media queries consistently:


// Define breakpoints
$breakpoints: (
  'xs': 0,
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px,
  'xxl': 1400px
);

// Media query mixin using the breakpoints map
@mixin media-up($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    $min-width: map-get($breakpoints, $breakpoint);
    @media (min-width: $min-width) {
      @content;
    }
  } @else {
    @warn "Breakpoint `#{$breakpoint}` not found in $breakpoints map.";
  }
}

// Usage
.container {
  width: 100%;
  padding: 0 15px;
  
  @include media-up('sm') {
    max-width: 540px;
    margin: 0 auto;
  }
  
  @include media-up('md') {
    max-width: 720px;
  }
  
  @include media-up('lg') {
    max-width: 960px;
  }
  
  @include media-up('xl') {
    max-width: 1140px;
  }
}

3. Spacing System

Create consistent spacing throughout your project:


// Define spacing scale
$spacing: (
  'xs': 0.25rem,  // 4px
  'sm': 0.5rem,   // 8px
  'md': 1rem,     // 16px
  'lg': 1.5rem,   // 24px
  'xl': 2rem,     // 32px
  'xxl': 3rem     // 48px
);

// Spacing function
@function spacing($size) {
  @if map-has-key($spacing, $size) {
    @return map-get($spacing, $size);
  }
  
  @warn "Spacing size `#{$size}` not found in $spacing map.";
  @return null;
}

// Spacing utility classes
@each $size-name, $size-value in $spacing {
  .m-#{$size-name} {
    margin: $size-value;
  }
  
  .mt-#{$size-name} {
    margin-top: $size-value;
  }
  
  .mb-#{$size-name} {
    margin-bottom: $size-value;
  }
  
  .p-#{$size-name} {
    padding: $size-value;
  }
  
  .pt-#{$size-name} {
    padding-top: $size-value;
  }
  
  .pb-#{$size-name} {
    padding-bottom: $size-value;
  }
}

// Usage in components
.card {
  margin-bottom: spacing('md');
  padding: spacing('lg');
}

.button {
  padding: spacing('sm') spacing('md');
}

Advanced Map Techniques

1. Nested Maps for Theme Configuration

Create comprehensive theme systems with nested maps:


// Define theme configuration
$theme-config: (
  'light': (
    'colors': (
      'background': #ffffff,
      'text': #212529,
      'primary': #0066cc,
      'secondary': #6c757d,
      'border': #dee2e6
    ),
    'shadows': (
      'sm': '0 1px 2px rgba(0, 0, 0, 0.05)',
      'md': '0 4px 6px rgba(0, 0, 0, 0.1)',
      'lg': '0 10px 15px rgba(0, 0, 0, 0.1)'
    ),
    'fonts': (
      'body': ('Roboto', 'Helvetica', 'Arial', sans-serif),
      'heading': ('Roboto', 'Helvetica', 'Arial', sans-serif),
      'monospace': ('SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace)
    )
  ),
  'dark': (
    'colors': (
      'background': #121212,
      'text': #e0e0e0,
      'primary': #4d94ff,
      'secondary': #909090,
      'border': #2a2a2a
    ),
    'shadows': (
      'sm': '0 1px 2px rgba(0, 0, 0, 0.2)',
      'md': '0 4px 6px rgba(0, 0, 0, 0.3)',
      'lg': '0 10px 15px rgba(0, 0, 0, 0.3)'
    ),
    'fonts': (
      'body': ('Roboto', 'Helvetica', 'Arial', sans-serif),
      'heading': ('Roboto', 'Helvetica', 'Arial', sans-serif),
      'monospace': ('SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace)
    )
  )
);

// Function to get theme values
@function theme($theme-name, $category, $key) {
  $theme: map-get($theme-config, $theme-name);
  
  @if $theme != null {
    $category-map: map-get($theme, $category);
    
    @if $category-map != null {
      @return map-get($category-map, $key);
    }
  }
  
  @warn "Theme value not found: #{$theme-name}, #{$category}, #{$key}";
  @return null;
}

// Mixin to generate theme variants
@mixin themed($theme-name) {
  .theme-#{$theme-name} {
    --background-color: #{theme($theme-name, 'colors', 'background')};
    --text-color: #{theme($theme-name, 'colors', 'text')};
    --primary-color: #{theme($theme-name, 'colors', 'primary')};
    --secondary-color: #{theme($theme-name, 'colors', 'secondary')};
    --border-color: #{theme($theme-name, 'colors', 'border')};
    --shadow-sm: #{theme($theme-name, 'shadows', 'sm')};
    --shadow-md: #{theme($theme-name, 'shadows', 'md')};
    --shadow-lg: #{theme($theme-name, 'shadows', 'lg')};
    
    background-color: var(--background-color);
    color: var(--text-color);
    
    @content;
  }
}

// Generate theme classes
@each $theme-name, $theme-values in $theme-config {
  @include themed($theme-name);
}

// Usage in components
.card {
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-sm);
  
  &__header {
    border-bottom: 1px solid var(--border-color);
  }
  
  &__title {
    color: var(--primary-color);
  }
}

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

2. Deep Map Merging

Create a function to deeply merge maps for configuration overrides:


// Deep map merge function
@function deep-map-merge($map1, $map2) {
  $result: $map1;
  
  @each $key, $value in $map2 {
    @if type-of(map-get($result, $key)) == 'map' and type-of($value) == 'map' {
      $result: map-merge($result, (
        $key: deep-map-merge(map-get($result, $key), $value)
      ));
    } @else {
      $result: map-merge($result, (
        $key: $value
      ));
    }
  }
  
  @return $result;
}

// Default configuration
$default-config: (
  'colors': (
    'primary': #0066cc,
    'secondary': #6c757d
  ),
  'breakpoints': (
    'sm': 576px,
    'md': 768px,
    'lg': 992px
  )
);

// Custom configuration overrides
$custom-config: (
  'colors': (
    'primary': #ff0000,  // Override primary color
    'accent': #ff9900    // Add new color
  ),
  'spacing': (           // Add new category
    'base': 8px
  )
);

// Merge configurations
$config: deep-map-merge($default-config, $custom-config);

// Result:
// $config: (
//   'colors': (
//     'primary': #ff0000,
//     'secondary': #6c757d,
//     'accent': #ff9900
//   ),
//   'breakpoints': (
//     'sm': 576px,
//     'md': 768px,
//     'lg': 992px
//   ),
//   'spacing': (
//     'base': 8px
//   )
// );

3. Dynamic Map Generation

Generate maps programmatically for advanced configuration:


// Generate color shades
@function generate-color-shades($base-color, $steps: 9) {
  $shades: ();
  
  @for $i from 1 through $steps {
    $lightness: 100% - (100% / $steps) * ($i - 1);
    $shade-name: 100 - ($i - 1) * (100 / $steps);
    
    $shades: map-merge($shades, (
      '#{$shade-name}': adjust-color($base-color, $lightness: $lightness - 50%)
    ));
  }
  
  @return $shades;
}

// Generate a color palette
@function generate-palette($colors-map) {
  $palette: ();
  
  @each $color-name, $base-color in $colors-map {
    $palette: map-merge($palette, (
      $color-name: generate-color-shades($base-color)
    ));
  }
  
  @return $palette;
}

// Base colors
$base-colors: (
  'blue': #0066cc,
  'green': #28a745,
  'red': #dc3545
);

// Generate complete palette
$color-palette: generate-palette($base-colors);

// Usage
.text-blue-500 {
  color: map-get(map-get($color-palette, 'blue'), '500');
}

.bg-green-200 {
  background-color: map-get(map-get($color-palette, 'green'), '200');
}

Creating a Configuration System with Maps

1. Configuration Architecture

Structure your configuration files:


// _config.scss
// Import all configuration files
@import 'config/colors';
@import 'config/typography';
@import 'config/spacing';
@import 'config/breakpoints';
@import 'config/z-index';
@import 'config/animations';

// _config-colors.scss
$config-colors: (
  'primary': #0066cc,
  'secondary': #6c757d,
  'success': #28a745,
  'danger': #dc3545,
  'warning': #ffc107,
  'info': #17a2b8
);

// _config-typography.scss
$config-typography: (
  'font-families': (
    'base': ('Roboto', 'Helvetica', 'Arial', sans-serif),
    'heading': ('Roboto', 'Helvetica', 'Arial', sans-serif),
    'monospace': ('SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace)
  ),
  'font-sizes': (
    'xs': 0.75rem,
    'sm': 0.875rem,
    'md': 1rem,
    'lg': 1.25rem,
    'xl': 1.5rem,
    'xxl': 2rem
  ),
  'font-weights': (
    'light': 300,
    'regular': 400,
    'medium': 500,
    'bold': 700
  ),
  'line-heights': (
    'tight': 1.2,
    'normal': 1.5,
    'loose': 1.8
  )
);

// _functions.scss
// Access configuration values
@function color($key) {
  @return map-get($config-colors, $key);
}

@function font-family($key) {
  @return map-get(map-get($config-typography, 'font-families'), $key);
}

@function font-size($key) {
  @return map-get(map-get($config-typography, 'font-sizes'), $key);
}

@function font-weight($key) {
  @return map-get(map-get($config-typography, 'font-weights'), $key);
}

@function line-height($key) {
  @return map-get(map-get($config-typography, 'line-heights'), $key);
}

2. Component Configuration

Configure components with maps:


// Component configuration
$button-config: (
  'padding': (
    'sm': (0.25rem 0.5rem),
    'md': (0.5rem 1rem),
    'lg': (0.75rem 1.5rem)
  ),
  'font-size': (
    'sm': font-size('xs'),
    'md': font-size('sm'),
    'lg': font-size('md')
  ),
  'border-radius': (
    'sm': 0.125rem,
    'md': 0.25rem,
    'lg': 0.5rem
  ),
  'variants': (
    'primary': (
      'background': color('primary'),
      'color': white,
      'border': darken(color('primary'), 10%)
    ),
    'secondary': (
      'background': color('secondary'),
      'color': white,
      'border': darken(color('secondary'), 10%)
    ),
    'success': (
      'background': color('success'),
      'color': white,
      'border': darken(color('success'), 10%)
    )
  )
);

// Button component mixin
@mixin button($size: 'md', $variant: 'primary') {
  display: inline-block;
  font-weight: font-weight('medium');
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  user-select: none;
  border: 1px solid transparent;
  transition: all 0.2s ease-in-out;
  
  // Size configuration
  padding: map-get(map-get($button-config, 'padding'), $size);
  font-size: map-get(map-get($button-config, 'font-size'), $size);
  border-radius: map-get(map-get($button-config, 'border-radius'), $size);
  
  // Variant configuration
  $variant-config: map-get(map-get($button-config, 'variants'), $variant);
  background-color: map-get($variant-config, 'background');
  color: map-get($variant-config, 'color');
  border-color: map-get($variant-config, 'border');
  
  &:hover {
    background-color: darken(map-get($variant-config, 'background'), 7.5%);
    border-color: darken(map-get($variant-config, 'border'), 10%);
  }
  
  &:focus {
    outline: 0;
    box-shadow: 0 0 0 0.2rem rgba(map-get($variant-config, 'background'), 0.25);
  }
}

// Usage
.btn-primary-md {
  @include button('md', 'primary');
}

.btn-secondary-sm {
  @include button('sm', 'secondary');
}

.btn-success-lg {
  @include button('lg', 'success');
}

Best Practices for SCSS Maps

1. Organization

  • Group related values in maps
  • Use consistent naming conventions
  • Separate configuration maps from implementation
  • Document map structures with comments

2. Accessibility

  • Create helper functions to access map values
  • Provide fallbacks for missing map values
  • Use descriptive error messages with @warn

3. Maintainability

  • Create a single source of truth for configuration
  • Allow for easy overrides with deep map merging
  • Keep maps focused on a single purpose
  • Document map structures for team members

4. Performance

  • Avoid excessive nesting in maps
  • Cache frequently accessed map values in variables
  • Use maps for configuration, not for runtime calculations

Resources and Further Reading

Documentation

Articles and Tutorials

Tools and Libraries