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 modern color.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:

FunctionWhat it doesExample
lighten($color, $amount)Increase lightnesslighten(#3b82f6, 20%)
darken($color, $amount)Decrease lightnessdarken(#3b82f6, 15%)
saturate($color, $amount)Increase saturationsaturate(#64748b, 30%)
desaturate($color, $amount)Decrease saturationdesaturate(#3b82f6, 20%)
adjust-hue($color, $degrees)Rotate hue on the color wheeladjust-hue(#3b82f6, 180deg)
complement($color)Get the complementary color (180° rotation)complement(#3b82f6)
mix($color1, $color2, $weight)Blend two colorsmix(#3b82f6, #fff, 50%)
rgba($color, $alpha)Set alpha transparencyrgba(#3b82f6, 0.5)
opacify($color, $amount)Make more opaqueopacify(rgba(black, 0.3), 0.2)
transparentize($color, $amount)Make more transparenttransparentize(#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

FunctionBehaviorBest for
color.adjust()Shift by fixed amountConsistent offsets across different colors
color.scale()Shift proportionallyShades that feel natural — avoids over-/under-shoot
color.change()Set absolute valueWhen 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

PracticeWhy
Test generated colors against WCAG 2.1 contrast ratioslighten() and color.scale() can produce low-contrast pairs
Use color.scale() over color.adjust() for shadesProportional scaling avoids extreme results on already-light/dark colors
Define text colors explicitly for each shadeDo not assume generated backgrounds will contrast with white or black text
Document your palette with hex valuesDevTools 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

  1. Legacy functions (lighten, darken, mix) still work but the sass:color module is preferred for new code.
  2. color.adjust() shifts channels by fixed amounts; color.scale() shifts proportionally; color.change() sets absolute values.
  3. color.mix() blends two colors by weight — useful for tints, shades, and overlays.
  4. Generate palettes programmatically with loops + color functions → consistent design tokens.
  5. Always verify contrast on generated colors — SASS cannot guarantee WCAG compliance automatically.

Explain-It Challenge

Explain without notes:

  1. What is the difference between color.adjust($c, $lightness: 20%) and color.scale($c, $lightness: 20%)?
  2. How would you generate five shades of a single brand color using SCSS?
  3. Why is it dangerous to assume generated color shades will meet accessibility contrast requirements?

Navigation: ← 1.7.h — Control Directives · 1.7 Overview →