Nested Maps & Deep Access
Model real design tokens with maps inside maps, then read and update them safely.
Once a project grows, a flat map stops being enough. Real design tokens are hierarchical: a colour has shades, a scale has steps, a component has variants. Sass lets you nest maps as deeply as you like, and the sass:map module gives you tools to walk that tree.
Reading a nested value
map.get() accepts multiple keys and drills down one level per key. No manual chaining, no intermediate variables.
@use 'sass:map';
$theme: (
'color': (
'brand': (
'base': #CC6699,
'dark': #A04B75,
),
),
);
// One call, three keys deep:
.btn { background: map.get($theme, 'color', 'brand', 'base'); } // #CC6699Updating without mutating
Maps are immutable, so map.set() and map.deep-merge() return a new map. map.set() also accepts a key path, and map.deep-merge() merges nested maps recursively instead of clobbering the whole branch the way map.merge() would.
| Function | What it does |
|---|---|
map.get($m, k1, k2) | Read down a key path |
map.set($m, k1, k2, v) | Return a copy with one path changed |
map.merge($a, $b) | Shallow merge — top level only |
map.deep-merge($a, $b) | Recursive merge of nested maps |
Think of a nested map as the single source of truth for your system. Everything else — utilities, components, themes — reads from it rather than hard-coding values.
Example
@use 'sass:map';
// A realistic, nested token tree
$tokens: (
'palette': (
'brand': (50: #f8e9f1, 500: #CC6699, 900: #5c2340),
'neutral': (50: #f7f7f8, 500: #6b7280, 900: #111827),
),
'radius': ('sm': 4px, 'md': 8px, 'lg': 16px),
);
// Safe, path-aware accessor
@function token($keys...) {
@if not map.has-key($tokens, $keys...) {
@error 'Unknown token path: #{$keys}';
}
@return map.get($tokens, $keys...);
}
.card {
background: token('palette', 'brand', 50);
color: token('palette', 'brand', 900);
border-radius: token('radius', 'md');
}
// Extend the system without touching the source map
$tokens: map.deep-merge($tokens, (
'palette': ('brand': (700: #7d3358))
));
// brand 50/500/900 survive; 700 is addedWhen to use it
- A design system stores its color palette as a nested map (color -> variant -> value) so components access brand.primary.500 through a helper function.
- A developer models typography tokens as a nested map keyed by role and property, then generates CSS custom properties by recursively iterating both levels.
- A responsive mixin library stores breakpoint and property data in a nested map so a single @each loop can emit all responsive utility classes.
More examples
Defining and reading a nested map
Defines a two-level color palette map and wraps the nested map.get() calls in a helper function for clean call sites.
@use 'sass:map';
$palette: (
blue: (
100: #dbeafe,
500: #3b82f6,
900: #1e3a8a
)
);
@function color($hue, $shade) {
@return map.get(map.get($palette, $hue), $shade);
}
.btn { background: color(blue, 500); }Updating a value in a nested map
Uses map.deep-merge() to update a single nested value without rebuilding the entire top-level map from scratch.
@use 'sass:map';
$tokens: (color: (primary: #2563eb), space: (base: 1rem));
// Merge an override into the color sub-map
$tokens: map.deep-merge($tokens, (color: (primary: #e74c3c)));
// Now tokens color.primary == #e74c3c
.btn { background: map.get(map.get($tokens, color), primary); }Iterating two-level nested maps
Iterates the outer map by name, then reads individual properties from each nested sub-map to emit typed typography utility classes.
@use 'sass:map';
$scale: (sm: (size: 0.875rem, weight: 400), lg: (size: 1.25rem, weight: 600));
@each $name, $props in $scale {
.text-#{$name} {
font-size: map.get($props, size);
font-weight: map.get($props, weight);
}
}
Discussion