This is an updated version of my old article SASS-only Material Angular Palette generator published in 2020. It’s updated to be compatible with Angular 18+ and Material 2.
mat-palette-gen.scss
@use 'sass:math';
@use 'sass:map';
@use 'sass:color';
$values: (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400, A700);
@mixin _output-palette-variables($map, $prefix) {
@each $key, $value in $map {
@if ($value) {
@if ($key == 'contrast') {
@include _output-palette-variables($value, '#{$prefix}-contrast');
} @else {
--#{$prefix}-#{$key}: #{$value};
}
}
}
}
@function createPalette($color) {
$white: #fff;
$black: #000;
$baseDark: multiply($color, $color);
$palette: (
50 : color.mix($color, $white, 12%),
100 : color.mix($color, $white, 30%),
200 : color.mix($color, $white, 50%),
300 : color.mix($color, $white, 70%),
400 : color.mix($color, $white, 85%),
500 : color.mix($color, $white, 100%),
600 : color.mix($color, $baseDark, 87%),
700 : color.mix($color, $baseDark, 70%),
800 : color.mix($color, $baseDark, 54%),
900 : color.mix($color, $baseDark, 25%),
A100: color.adjust(color.mix($black, $baseDark, 15%), $lightness: 65%, $saturation: 80%),
A200: color.adjust(color.mix($black, $baseDark, 15%), $lightness: 55%, $saturation: 80%),
A400: color.adjust(color.mix($black, $baseDark, 15%), $lightness: 45%, $saturation: 100%),
A700: color.adjust(color.mix($black, $baseDark, 15%), $lightness: 40%, $saturation: 100%),
);
$contrast: ();
@each $v in $values {
$contrast: map.merge($contrast, ($v: getContrast(map.get($palette, $v))))
}
$palette: map.merge($palette, (contrast: $contrast));
@return $palette;
}
@function multiply($rgb1, $rgb2) {
$r: math.floor(math.div(color.channel($rgb1, "red", $space: rgb) * color.channel($rgb2, "red", $space: rgb), 255));
$g: math.floor(math.div(color.channel($rgb1, "green", $space: rgb) * color.channel($rgb2, "green", $space: rgb), 255));
$b: math.floor(math.div(color.channel($rgb1, "blue", $space: rgb) * color.channel($rgb2, "blue", $space: rgb), 255));
@return rgb($r, $g, $b);
}
@function getBrightness($color) {
@return math.div(color.channel($color, "red", $space: rgb) * 299 + color.channel($color, "green", $space: rgb) * 587 + color.channel($color, "blue", $space: rgb) * 114, 1000);
}
@function isLight($color) {
@return getBrightness($color) >= 128;
}
@function getContrast($color) {
@if isLight($color) {
@return #000;
} @else {
@return #fff;
}
}
Usage
SCSS
@use '@angular/material' as mat;
@use 'sass:map';
// If you want the `mat-app-background` class
@include mat.app-background();
$primary-color: #6738d6;
$accent-color: #ebb652;
$warn-color: #d13212;
$primary-palette: createPalette($primary-color);
$accent-palette: createPalette($accent-color);
$warn-palette: createPalette($warn-color);
$primary: mat.m2-define-palette($primary-palette);
$accent: mat.m2-define-palette($accent-palette);
$warn: mat.m2-define-palette($warn-palette);
$theme: mat.m2-define-light-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn
),
));
// If you want the contrast colors
$primary-contrast-color: map.get(map.get($primary-palette, 'contrast'), 500);
$accent-contrast-color: map.get(map.get($accent-palette, 'contrast'), 500);
$warn-contrast-color: map.get(map.get($warn-palette, 'contrast'), 500);
// If you want to have CSS vars
:root {
--primary: #{$primary-color};
--accent: #{$accent-color};
--warn: #{$error-color};
--primary-contrast: #{$primary-contrast-color};
--accent-contrast: #{$accent-contrast-color};
--warn-contrast: #{$warn-contrast-color};
/*
These will output ALL the palette colors as CSS vars
EXAMPLE:
@include _output-palette-variables($primary-palette, 'primary')
OUTPUT:
--primary-50: rgb(236.76, 231.12, 250.08);
--primary-100: rgb(209.4, 195.3, 242.7);
--primary-200: rgb(179, 155.5, 234.5);
--primary-300: rgb(148.6, 115.7, 226.3);
--primary-400: rgb(125.8, 85.85, 220.15);
--primary-500: #6738d6;
--primary-600: rgb(94.94, 50.28, 209.45);
--primary-700: rgb(84.4, 42.8, 203.5);
--primary-800: rgb(74.48, 35.76, 197.9);
--primary-900: rgb(56.5, 23, 187.75);
--primary-A100: hsl(250.4191616766, 167.4345549738%, 96.8333333333%);
--primary-A200: hsl(250.4191616766, 167.4345549738%, 86.8333333333%);
--primary-A400: hsl(250.4191616766, 187.4345549738%, 76.8333333333%);
--primary-A700: hsl(250.4191616766, 187.4345549738%, 71.8333333333%);
--primary-contrast-50: #000;
--primary-contrast-100: #000;
--primary-contrast-200: #000;
--primary-contrast-300: #000;
--primary-contrast-400: #fff;
--primary-contrast-500: #fff;
--primary-contrast-600: #fff;
--primary-contrast-700: #fff;
--primary-contrast-800: #fff;
--primary-contrast-900: #fff;
--primary-contrast-A100: #000;
--primary-contrast-A200: #000;
--primary-contrast-A400: #fff;
--primary-contrast-A700: #fff;
*/
@include _output-palette-variables($primary-palette, 'primary');
@include _output-palette-variables($accent-palette, 'accent');
@include _output-palette-variables($warn-palette, 'warn');
}
@include mat.all-component-themes($theme);