Color Functions
Sass’s sass:color module gives you precise programmatic control over colors — lighten, darken, mix, adjust hue, derive accessible contrasts. Pair with CSS custom properties at runtime for theming and you cover both compile-time palettes and runtime dark mode.
color.adjust, mix, contrast, palettes
EXAMPLE
// 1) Modern modular Sass — always @use, not @import
@use 'sass:color';
@use 'sass:math';
@use 'sass:list';
// 2) Color formats
$hex : #4f46e5;
$rgb : rgb(79, 70, 229);
$rgba : rgba(79, 70, 229, 0.5);
$hsl : hsl(244, 76%, 59%);
$hsla : hsla(244, 76%, 59%, 0.5);
$named : indigo;
// All interchangeable in Sass operations.
// 3) Channel access
color.red ($hex) // 79
color.green($hex) // 70
color.blue ($hex) // 229
color.hue ($hex) // 244deg
color.saturation($hex) // 76%
color.lightness ($hex) // 59%
color.alpha ($hex) // 1
// 4) color.adjust — fine-tune by absolute amounts
color.adjust($hex, $lightness: 10%) // lighten by 10 points
color.adjust($hex, $lightness: -10%) // darken
color.adjust($hex, $saturation: -20%, $hue: 30deg)
color.adjust($hex, $alpha: -0.2) // opacity drop
// 5) color.scale — proportional (better than adjust for big swings)
color.scale($hex, $lightness: 50%) // halfway to white
color.scale($hex, $lightness: -50%) // halfway to black
color.scale($hex, $alpha: -50%) // half as opaque
// scale is multiplicative; adjust is additive. Reach for scale for tints/shades.
// 6) color.mix — blend two colors
color.mix(red, blue) // 50/50
color.mix(red, blue, 25%) // 25% red, 75% blue
color.mix($hex, white, 90%) // very light tint of $hex
color.mix($hex, black, 90%) // deep shade
// 7) Generate a 11-step palette from a single seed
@function palette($base) {
$out: ();
$out: map-merge($out, ( '50': color.mix(white, $base, 90%) ));
$out: map-merge($out, ( '100': color.mix(white, $base, 80%) ));
$out: map-merge($out, ( '200': color.mix(white, $base, 65%) ));
$out: map-merge($out, ( '300': color.mix(white, $base, 45%) ));
$out: map-merge($out, ( '400': color.mix(white, $base, 20%) ));
$out: map-merge($out, ( '500': $base ));
$out: map-merge($out, ( '600': color.mix(black, $base, 15%) ));
$out: map-merge($out, ( '700': color.mix(black, $base, 30%) ));
$out: map-merge($out, ( '800': color.mix(black, $base, 50%) ));
$out: map-merge($out, ( '900': color.mix(black, $base, 70%) ));
$out: map-merge($out, ( '950': color.mix(black, $base, 85%) ));
@return $out;
}
$indigo: palette(#4f46e5);
.btn { background: map-get($indigo, '500'); }
.btn:hover { background: map-get($indigo, '600'); }
// 8) Emit as CSS custom properties — runtime theming
@use 'sass:map';
@mixin emit-palette($name, $palette) {
@each $shade, $value in $palette {
--\#{$name}-\#{$shade}: #{$value};
}
}
:root {
@include emit-palette('indigo', $indigo);
}
.btn {
background: var(--indigo-500);
color: white;
}
.btn:hover { background: var(--indigo-600); }
// 9) Contrast helpers — pick black/white text for accessibility
@function readable-color($bg, $light: white, $dark: black, $threshold: 60%) {
@if color.lightness($bg) > $threshold {
@return $dark;
}
@return $light;
}
.btn-primary {
background: $hex;
color: readable-color($hex);
}
// 10) WCAG contrast — proper calculation
@function luminance($c) {
$r: math.div(color.red($c), 255);
$g: math.div(color.green($c), 255);
$b: math.div(color.blue($c), 255);
$r: if($r <= 0.03928, math.div($r, 12.92), math.pow(math.div($r + 0.055, 1.055), 2.4));
$g: if($g <= 0.03928, math.div($g, 12.92), math.pow(math.div($g + 0.055, 1.055), 2.4));
$b: if($b <= 0.03928, math.div($b, 12.92), math.pow(math.div($b + 0.055, 1.055), 2.4));
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}
@function contrast-ratio($a, $b) {
$la: luminance($a) + 0.05;
$lb: luminance($b) + 0.05;
@return if($la > $lb, math.div($la, $lb), math.div($lb, $la));
}
// Pass WCAG AA for body text: ratio >= 4.5
// 11) Modern CSS color-mix() at runtime
.btn:hover {
background: color-mix(in srgb, var(--accent), white 10%); // 10% white blend
}
.btn:active {
background: color-mix(in oklab, var(--accent), black 12%);
}
// color-mix() in oklab/oklch gives perceptually uniform blends — better than srgb for design systems.
// 12) Dark mode palette inversion (smart-ish)
@function dark-palette($palette) {
$keys: list.reverse(map.keys($palette));
$values: map.values($palette);
$out: ();
@for $i from 1 through length($keys) {
$key: list.nth($keys, $i);
$val: list.nth($values, $i);
$out: map.set($out, $key, $val);
}
@return $out;
}
// 13) Theming variants
$light: (
'bg': white,
'fg': #0f172a,
'border': #e2e8f0,
'accent': #4f46e5,
);
$dark: (
'bg': #0f172a,
'fg': #f8fafc,
'border': #1e293b,
'accent': #818cf8,
);
@mixin theme($map) {
@each $k, $v in $map {
--\#{$k}: #{$v};
}
}
:root { @include theme($light); }
[data-theme='dark'] { @include theme($dark); }
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) { @include theme($dark); }
}
// 14) Common bugs
// • Using lighten()/darken() — deprecated in modern Sass; use color.scale / color.adjust
// • @import — deprecated; use @use
// • Hard-coded hex everywhere — bypass design tokens; centralise + reference
// • Adjusting alpha with color.adjust($alpha: 0.5) — that's absolute; use scale for proportional
// • Mixing 'in srgb' for design accents — non-uniform; try 'in oklab' for perceptual blending
// • Generated palette has inconsistent contrast — verify each step meets WCAG AA at intended use
// • Computing colors at runtime via JS instead of CSS color-mix — extra JS for a CSS feature
Why it matters
Use Sass’s color module for build-time palette generation (mix toward white/black for tints/shades), emit the palette as CSS custom properties, and lean on runtime color-mix(in oklab, ...) for perceptual hover states. Always verify WCAG contrast for every shade you ship.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
color: lighten(#04AA6D, 10%); background: darken(#04AA6D, 10%); // Modern: color: color.scale($base, $lightness: 10%);Try it Yourself »
Discussion
Loading…