Chapter 16: Sass Color Functions
Sass Color Functions — the tools that make Sass shine when building themes, hover states, disabled buttons, dark mode variants, and consistent color systems.
In 2026 (Dart Sass reality, version ~1.97+), color manipulation has evolved a lot. The classic darken(), lighten(), saturate(), etc. are deprecated (they still work with warnings in most setups, but will eventually disappear).
The modern, recommended way is to use the sass:color module with these main functions:
- color.adjust() — fixed amount changes (absolute shifts)
- color.scale() — percentage-based relative changes (usually better for themes)
- color.change() — set specific channel values directly
- color.mix() — blend two colors
- color.channel() — read individual channel values
- And support for modern color spaces like oklch, lab, lch, display-p3, etc. (huge win since ~1.79.0)
1. Loading the Module (Modern 2026 Way)
Always start with:
|
0 1 2 3 4 5 6 7 |
@use 'sass:color' as c; // or @use 'sass:color'; → color.scale(...) |
We’ll use the short c. alias in examples — very common in real code.
2. Core Functions – Detailed with Examples
Let’s take a base color and play with it:
|
0 1 2 3 4 5 6 |
$primary: #6c5ce7; // hsl(249, 70%, 60%) roughly |
A. color.adjust($color, $lightness: …, $alpha: …, $space: …)
→ Add/subtract fixed amounts to channels (absolute change)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Make 20 units darker in lightness (HSL default space) darker: c.adjust($primary, $lightness: -20); // ≈ #4a3bc7 // Increase saturation by 15 units + drop alpha c.adjust($primary, $saturation: 15, $alpha: -0.3); // Use modern space (better perceptual uniformity) c.adjust($primary, $lightness: -15, $space: oklch); |
Real usage – hover state (fixed shift):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
.btn { background: $primary; &:hover { background: c.adjust($primary, $lightness: -12); } } |
Heads up: Fixed amounts feel inconsistent across different hues (a 20-unit shift on a bright yellow looks different than on blue). → prefer scale() most times.
B. color.scale($color, $lightness: …, $saturation: …, $alpha: …, $space: …)
→ Scale channels by percentage (relative change) — this is usually the best choice in 2026
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Darken by 12% of current lightness c.scale($primary, $lightness: -12%); // perceptual, safe // Boost saturation 30% relative to current c.scale($primary, $saturation: 30%); // Fade out 40% (multiply alpha by 0.6) c.scale($primary, $alpha: -40%); // Modern space (oklch recommended for better results) c.scale($primary, $lightness: -15%, $space: oklch); |
Real usage – theme variants (very common pattern):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@use 'sass:color' as c; $primary: #6c5ce7; .btn { background: $primary; color: white; &--light { background: c.scale($primary, $lightness: 15%); } &--dark { background: c.scale($primary, $lightness: -20%); } &--muted { background: c.scale($primary, $saturation: -40%); } &:disabled { background: c.scale($primary, $lightness: 25%, $saturation: -30%); opacity: 0.7; } } |
C. color.change($color, $lightness: …, $hue: …, $space: …)
→ Set absolute channel values (replace, not adjust)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Force lightness to 70% (ignore original) c.change($primary, $lightness: 70%); // Change hue to 180° (cyan territory) c.change($primary, $hue: 180deg); // In modern space c.change($primary, $lightness: 0.75, $space: oklch); |
Use case: Reset to specific values in themes, generate monochromatic palettes.
D. color.mix($color1, $color2, $weight: 50%)
→ Blend two colors (like gradient stop at $weight %)
|
0 1 2 3 4 5 6 7 8 |
$danger: #e74c3c; mixed: c.mix($primary, $danger, 30%); // 70% primary + 30% danger |
Real usage – gradient base or warning states:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
.alert-warning { background: linear-gradient( to right, c.mix(#f1c40f, white, 80%), #f1c40f ); } |
E. color.channel($color, $channel, $space: …)
→ Read a single channel value (lightness, hue, red, etc.)
|
0 1 2 3 4 5 6 7 8 9 10 |
@debug c.channel($primary, "lightness"); // ≈ 60% @debug c.channel($primary, "hue"); // ≈ 249deg // In oklch space @debug c.channel($primary, "lightness", oklch); |
Use case: Conditional logic based on color properties.
|
0 1 2 3 4 5 6 7 8 9 10 |
@if c.channel($bg, "lightness") > 70% { color: black; } @else { color: white; } |
3. Modern 2026 Best Practices & Reality Check
-
Prefer color.scale() over adjust() — relative changes look more natural across hues
-
Use $space: oklch (or lch) for almost everything — perceptually uniform (colors feel equally bright/dark)
-
Legacy functions (darken($color, 10%)) → deprecated → migrate to:
SCSS012345678910// Old: darken($primary, 12%)// New:c.adjust($primary, $lightness: -12); // fixed// or better:c.scale($primary, $lightness: -12%); // relative -
Many teams use a color utility function wrapper:
SCSS0123456789101112@function tint($color, $percent) {@return c.scale($color, $lightness: $percent%, $space: oklch);}@function shade($color, $percent) {@return c.scale($color, $lightness: -$percent%, $space: oklch);} -
CSS is catching up fast (color-mix(), relative color syntax in modern browsers), but Sass still wins for compile-time consistency + complex logic.
4. Quick Challenge – Try This!
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
@use 'sass:color' as c; $brand: #9b59b6; .success-variant { background: c.scale($brand, $hue: 80deg, $saturation: -20%, $lightness: 10%); // Play with values! } |
Compile and see the magic!
You’ve now got modern Sass color superpowers — this is how 2026 design systems stay consistent and beautiful across light/dark modes.
Next — want to build a full color theme system with maps + scale + oklch? Or combine color functions with mixins for button families? Or migrate old darken/lighten code step-by-step?
Just tell me — class is going great! 🚀
