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
- Using Sass Maps - SitePoint
- Introducing Sass Modules - CSS-Tricks
- Using Sass in a Smart Way - Smashing Magazine
Tools and Libraries
- Accoutrement - Sass utilities for design systems
- Sass MQ - Media query manager using maps