Episode 1 — Fundamentals / 1.7 — Working With SASS
1.7.i — Color Functions
In one sentence: SASS provides a rich set of color manipulation functions — from simple
lighten()/darken()to the moderncolor.adjust()/color.scale()API — that let you build consistent color palettes programmatically instead of hand-picking every shade.
Navigation: ← 1.7.h — Control Directives · 1.7 Overview →
1. Legacy global color functions
These are the classic functions you will see in older codebases and tutorials:
| Function | What it does | Example |
|---|---|---|
lighten($color, $amount) | Increase lightness | lighten(#3b82f6, 20%) |
darken($color, $amount) | Decrease lightness | darken(#3b82f6, 15%) |
saturate($color, $amount) | Increase saturation | saturate(#64748b, 30%) |
desaturate($color, $amount) | Decrease saturation | desaturate(#3b82f6, 20%) |
adjust-hue($color, $degrees) | Rotate hue on the color wheel | adjust-hue(#3b82f6, 180deg) |
complement($color) | Get the complementary color (180° rotation) | complement(#3b82f6) |
mix($color1, $color2, $weight) | Blend two colors | mix(#3b82f6, #fff, 50%) |
rgba($color, $alpha) | Set alpha transparency | rgba(#3b82f6, 0.5) |
opacify($color, $amount) | Make more opaque | opacify(rgba(black, 0.3), 0.2) |
transparentize($color, $amount) | Make more transparent | transparentize(#3b82f6, 0.4) |
$primary: #3b82f6;
.btn {
background: $primary;
&:hover {
background: darken($primary, 10%);
}
&:active {
background: darken($primary, 20%);
}
&:disabled {
background: desaturate($primary, 40%);
opacity: 0.6;
}
}
2. Modern module-based functions
Dart Sass recommends using sass:color module functions instead of the global ones:
@use 'sass:color';
color.adjust() — absolute changes
Shifts a channel by a fixed amount:
color.adjust(#3b82f6, $lightness: 20%) // lighten by 20 percentage points
color.adjust(#3b82f6, $lightness: -15%) // darken by 15 percentage points
color.adjust(#3b82f6, $hue: 180deg) // rotate hue
color.adjust(#3b82f6, $alpha: -0.3) // make 30% more transparent
color.scale() — proportional changes
Scales a channel relative to its current position toward the maximum or minimum:
color.scale(#3b82f6, $lightness: 30%) // 30% toward white
color.scale(#3b82f6, $lightness: -30%) // 30% toward black
color.scale(#3b82f6, $saturation: -50%) // 50% toward gray
color.change() — set absolute values
Replaces a channel value entirely:
color.change(#3b82f6, $lightness: 90%) // set lightness to exactly 90%
color.change(#3b82f6, $alpha: 0.5) // set alpha to exactly 0.5
When to use which
| Function | Behavior | Best for |
|---|---|---|
color.adjust() | Shift by fixed amount | Consistent offsets across different colors |
color.scale() | Shift proportionally | Shades that feel natural — avoids over-/under-shoot |
color.change() | Set absolute value | When you know the exact target channel value |
3. color.mix() — blending
@use 'sass:color';
$primary: #3b82f6;
$white: #ffffff;
$black: #000000;
.light-bg { background: color.mix($primary, $white, 20%); } // mostly white
.medium-bg { background: color.mix($primary, $white, 50%); } // even mix
.dark-bg { background: color.mix($primary, $black, 70%); } // mostly primary
The weight is the proportion of the first color. mix($a, $b, 25%) = 25% $a + 75% $b.
4. Building color palettes programmatically
Shade scale from a single base
@use 'sass:color';
@use 'sass:map';
$palette-blue: ();
@for $i from 1 through 9 {
$shade: $i * 100;
$lightness: 95% - ($i * 10%);
$palette-blue: map.merge($palette-blue, ($shade: color.change(#3b82f6, $lightness: $lightness)));
}
// Use: map.get($palette-blue, 300) → light blue
// Use: map.get($palette-blue, 700) → dark blue
Generate CSS custom properties
@use 'sass:color';
$base-colors: (
'blue': #3b82f6,
'green': #22c55e,
'red': #ef4444,
);
:root {
@each $name, $base in $base-colors {
--color-#{$name}-100: #{color.scale($base, $lightness: 80%)};
--color-#{$name}-300: #{color.scale($base, $lightness: 40%)};
--color-#{$name}-500: #{$base};
--color-#{$name}-700: #{color.scale($base, $lightness: -40%)};
--color-#{$name}-900: #{color.scale($base, $lightness: -70%)};
}
}
Output gives you 15 CSS custom properties from 3 base colors — consistent, systematic, and easy to extend.
5. Accessibility considerations
Contrast checking in SCSS
Color functions make it easy to generate shades, but generated colors may fail WCAG contrast requirements. A slight lighten() on a background might drop text contrast below 4.5:1.
@use 'sass:color';
@function meets-contrast($bg, $text, $ratio: 4.5) {
$bg-lum: color.lightness($bg);
$text-lum: color.lightness($text);
// Simplified heuristic — not a true WCAG luminance calculation
@if $bg-lum > 50% and $text-lum > 50% {
@return false;
}
@if $bg-lum < 30% and $text-lum < 30% {
@return false;
}
@return true;
}
Real contrast checks require relative luminance calculations that are complex in SCSS. For production, verify generated palettes with tools like WebAIM Contrast Checker or axe DevTools.
Best practices
| Practice | Why |
|---|---|
| Test generated colors against WCAG 2.1 contrast ratios | lighten() and color.scale() can produce low-contrast pairs |
Use color.scale() over color.adjust() for shades | Proportional scaling avoids extreme results on already-light/dark colors |
| Define text colors explicitly for each shade | Do not assume generated backgrounds will contrast with white or black text |
| Document your palette with hex values | DevTools show computed values, but source documentation helps the team |
6. Practical recipes
Hover/active states from base color
@use 'sass:color';
@mixin interactive-color($base) {
background-color: $base;
&:hover { background-color: color.scale($base, $lightness: -15%); }
&:active { background-color: color.scale($base, $lightness: -25%); }
}
.btn-primary { @include interactive-color(#3b82f6); }
.btn-success { @include interactive-color(#22c55e); }
Overlay tint
@use 'sass:color';
@function tint($color, $percentage) {
@return color.mix(white, $color, $percentage);
}
@function shade($color, $percentage) {
@return color.mix(black, $color, $percentage);
}
.card { background: tint(#3b82f6, 90%); } // very light blue tint
7. Key takeaways
- Legacy functions (
lighten,darken,mix) still work but thesass:colormodule is preferred for new code. color.adjust()shifts channels by fixed amounts;color.scale()shifts proportionally;color.change()sets absolute values.color.mix()blends two colors by weight — useful for tints, shades, and overlays.- Generate palettes programmatically with loops + color functions → consistent design tokens.
- Always verify contrast on generated colors — SASS cannot guarantee WCAG compliance automatically.
Explain-It Challenge
Explain without notes:
- What is the difference between
color.adjust($c, $lightness: 20%)andcolor.scale($c, $lightness: 20%)? - How would you generate five shades of a single brand color using SCSS?
- Why is it dangerous to assume generated color shades will meet accessibility contrast requirements?
Navigation: ← 1.7.h — Control Directives · 1.7 Overview →