Skip to content

Instantly share code, notes, and snippets.

@brlodi
Last active February 14, 2024 14:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brlodi/e18ce2947664aacbbd9186d24608ff7a to your computer and use it in GitHub Desktop.
Save brlodi/e18ce2947664aacbbd9186d24608ff7a to your computer and use it in GitHub Desktop.
A set of Sass functions to automatically create Material Design-style color ranges, for when you don't want to have to manually spec out a full set of shades and tints for every color in your design.

About

These functions use one or two 'base' colors to automatically generate a color range similar to Material Design's color ranges. Useful to quickly get different shades of your primary brand color or accent color, or to generate a set of neutrals between hand-selected light and dark colors.

Usage

$greens: make-shades(#cc99cc);
// (
//   100: #eeddee,
//   200: #ddbbdd,
//   300: #cc99cc,
//   400: #bb77bb,
//   500: #aa55aa,
//   600: #884488,
//   700: #663366,
//   800: #442244,
//   900: #221122
// )

$grays: make-shades(#0a0a22, #f0f000);
// (
//   100: #ddeedd,
//   200: #bbddbb,
//   300: #99cc99,
//   400: #80aa8b,
//   500: #66887d,
//   600: #4d666f,
//   700: #334460,
//   800: #1a2252,
//   900: #000044
// )

$gray2: make-shades(#f0f000, #0a0a22);
// (
//   100: #ddeedd,
//   200: #bbddbb,
//   300: #99cc99,
//   400: #80aa8b,
//   500: #66887d,
//   600: #4d666f,
//   700: #334460,
//   800: #1a2252,
//   900: #000044
// )

Notice that order doesn't matter when passing two colors.

Example

I usually use it like this:

$palette: (
  primary: make-shades($cc99cc),
  accent: make-shades($cccc99),
  gray: make-shades(#fff4ff, #000044),
);

@function palette-color($name, $level: 500) {
  @return map-get(map-get($palette, $name), $level);
}

a {
  color: palette-color(primary, 700);
  &:hover {
    palette-color(primary);
  }
}

You can also of course use the output of one invocation to feed another; for example, I'll often use the 900 value of my accent color with white to generate the gray range, which has the result of slightly tinting the grays toward the accent color.

// Returns a Material Design-style value level of a color, i.e. a multiple of
// 100 in the range 100-900 inclusive.
//
// @param color $color
// @return number - the color's value level
@function color-level($color) {
@return min(max(round((100 - lightness($color)/1%) / 10) * 100, 100), 900);
}
// Generates a map containing a full range of value steps.
// - If one color is provided, that color is assigned to the closest value step
// and other colors are generated automatically.
// - If two colors are provided, both colors are assigned to their respective
// closest value step and the value steps between are interpolated. Value steps
// outside that range are generated automatically from the closest of the two
// colors.
//
// @requires color-level
// @param color $color1
// @param color $color2 [null]
// @return map - a map containing keys {100, 200, .., 900} and corresponding
// color values
@function make-shades($color1, $color2: null) {
$level1: color-level($color1);
$shades: ();
@if (type-of($color2) != color) {
@for $i from 1 through 9 {
@if $i*100 == $level1 {
$shades: map-merge($shades, ($i*100: $color1));
} @else {
$shades: map-merge($shades, ($i*100: change-color($color1, $lightness: 100-($i*10))));
}
}
} @else {
$level2: color-level($color2);
// We always want to work light-to-dark (100, 200, ..., 900)
@if $level2 < $level1 {
$tmp: $color1;
$color1: $color2;
$color2: $tmp;
$tmp: $level1;
$level1: $level2;
$level2: $tmp;
}
@if $level1 > 100 {
@for $i from 1 to $level1/100 {
$shades: map-merge($shades, ($i*100: change-color($color1, $lightness: 100-($i*10))));
}
}
$shades: map-merge($shades, ($level1: $color1));
@for $i from $level1/100+1 to $level2/100 {
$weight: 100% - (($i - $level1/100) / ($level2/100 - $level1/100))*100%;
$shades: map-merge($shades, ($i*100: mix($color1, $color2, $weight)));
}
$shades: map-merge($shades, ($level2: $color2));
@if $level2 < 900 {
@for $i from $level2/100+1 through 9 {
$shades: map-merge($shades, ($i*100: change-color($color2, $lightness: 100-($i*10))));
}
}
}
@return $shades;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment