Spruce CSS stores every color in one nested Sass map, $colors, and outputs each of them as a CSS custom property. That is what makes the dark mode and the design styles possible.
These are the default colors, grouped as in the $colors map. The swatches use the live custom properties, so they follow the theme mode and the style you pick in the header. Access any of them with the color() function: color('name', 'group').
Alert
Base
Btn
Form
Selection
Scrollbar
Table
Some keys are null by default (like background-readonly or the table heading-background). Spruce doesn’t output anything for them until you give them a value.
To avoid repetition, the map uses a few helper variables for the generic colors. Overwrite them to retheme Spruce quickly, or set the map values one by one.
@use 'sprucecss/scss/spruce' with (
$color-black: hsl(205deg 100% 2%),
$color-danger: hsl(0deg 71% 51%),
$color-gray: hsl(208deg 9% 42%),
$color-gray-light: hsl(215deg 63% 93%),
$color-primary: hsl(262deg 71% 49%),
$color-secondary: hsl(227deg 92% 55%),
$color-success: hsl(150deg 100% 33%),
$color-white: hsl(0deg 0% 100%),
);Below is the full declaration of the $colors map (from the installed version).
$colors: () !default;
$colors: map.deep-merge(
(
'alert': (
'danger': $color-danger,
'info': hsl(195deg 100% 42%),
'success': $color-success,
'warning': hsl(48deg 89% 55%),
),
'base': (
'background': $color-white,
'blockquote-border': $color-primary,
'border': $color-gray-light,
'border-strong': hsl(214deg 20% 78%),
'code-background': color.change($color-primary, $lightness: 97%),
'code-foreground': $color-black,
'focus-ring': $color-primary,
'heading': $color-black,
'kbd-border': hsl(214deg 32% 84%),
'link': $color-primary,
'link-hover': color.scale($color-primary, $lightness: -20%),
'mark-background': hsl(50deg 100% 80%),
'mark-foreground': $color-black,
'marker': $color-primary,
'muted': hsl(208deg 8% 46%),
'overlay': hsl(205deg 100% 2% / 55%),
'pre-background': color.change($color-primary, $lightness: 97%),
'pre-foreground': $color-black,
'primary': $color-primary,
'secondary': $color-secondary,
'strong': $color-black,
'surface': $color-white,
'surface-muted': hsl(210deg 40% 98%),
'text': $color-gray,
),
'btn': (
'danger-background': $color-danger,
'danger-background-hover': color.adjust($color-danger, $lightness: -10%),
'danger-foreground': $color-white,
'primary-background': $color-primary,
'primary-background-hover': color.adjust($color-primary, $lightness: -10%),
'primary-foreground': $color-white,
'primary-shadow': color.adjust($color-primary, $lightness: 35%),
'secondary-background': $color-secondary,
'secondary-background-hover': color.adjust($color-secondary, $lightness: -10%),
'secondary-foreground': $color-white,
'secondary-shadow': color.adjust($color-secondary, $lightness: 35%),
),
'form': (
'background': $color-white,
'background-disabled': hsl(0deg 0% 95%),
'background-readonly': null,
'border': hsl(260deg 4% 70%),
'border-disabled': $color-gray-light,
'border-focus': $color-primary,
'check-background': $color-primary,
'check-focus-ring': $color-primary,
'check-foreground': $color-white,
'group-label-background': hsl(210deg 60% 98%),
'group-label-foreground': $color-gray,
'invalid': $color-danger,
'invalid-focus-ring': color.adjust($color-danger, $alpha: -0.75),
'label': $color-black,
'legend': $color-black,
'placeholder': hsl(208deg 7% 40%),
'progress-background': $color-gray-light,
'progress-foreground': $color-primary,
'range-thumb-background': $color-primary,
'range-thumb-focus-ring': $color-primary,
'range-track-background': $color-gray-light,
'required': $color-danger,
'ring-focus': color.adjust($color-primary, $alpha: -0.75),
'select-foreground': $color-black,
'switch-background': $color-primary,
'switch-focus-ring': $color-primary,
'switch-foreground': $color-white,
'text': $color-gray,
'valid': $color-success,
'valid-focus-ring': color.adjust($color-success, $alpha: -0.75),
),
'selection': (
'foreground': $color-white,
'background': $color-primary,
),
'scrollbar': (
'thumb-background': hsl(0deg 0% 0% / 15%),
'thumb-background-hover': hsl(0deg 0% 0% / 25%),
'track-background': hsl(0deg 0% 0% / 5%),
),
'table': (
'border': $color-gray-light,
'caption': $color-gray,
'heading': $color-black,
'heading-background': null,
'hover': hsl(0deg 0% 0% / 5%),
'stripe': hsl(0deg 0% 0% / 2.5%),
'text': $color-gray,
),
),
$colors
);We declare the colors with the modern HSL syntax; Sass converts them in the output.
| Group | Key | Used by |
|---|---|---|
base | border-strong | Stronger dividers, empty states. |
base | focus-ring | The summary focus ring and the components. |
base | kbd-border | The kbd element. |
base | muted | Secondary text (.text-muted, descriptions). |
base | overlay | The dialog backdrop. |
base | pre-background, pre-foreground | Code blocks. |
base | surface, surface-muted | Cards and panels (follows the styles). |
btn | danger-* | The danger button variants. |
form | background-readonly | Read-only form controls (null by default). |
form | progress-background, progress-foreground | progress and meter. |
form | required | The .form-label--required marker. |
table | heading-background | The table header row (null by default). |
Spruce generates prefixed CSS variables at :root by group from the nested map. You can access a generated variable by name, but it is easier to use the getter function, color(). For the raw Sass value, use color-value().
a {
color: color('primary'); // var(--spruce-base-color-primary)
}
.control {
background-color: color('primary-background', 'btn');
}
.overlay {
// Runtime mixes that follow the theme and the style:
background-color: color-alpha('heading', 0.6);
border-color: color-tint('primary', 80%);
}Because the colors are in a Sass map, it is easy to overwrite them or add new values and groups.
@use 'sprucecss/scss/spruce' with (
$colors: (
'base': (
'primary': hsl(0deg 0% 15%),
'tertiary': hsl(332deg 49% 29%),
),
'my-custom-component': (
'background': hsl(0deg 0% 100%),
),
),
);A new group is output as --spruce-my-custom-component-color-background, and you get it with color('background', 'my-custom-component').
The $dark-colors map holds your dark mode colors. It is deep-merged into the built-in $default-dark-colors palette by the dark mode generator, so you only set what differs.
The default dark palette
$dark-color-background: hsl(206deg 100% 7%) !default;
$dark-color-border: hsl(0deg 0% 100% / 10%) !default;
$dark-color-heading: hsl(0deg 0% 95%) !default;
$dark-color-primary: hsl(261deg 54% 70%) !default;
$dark-color-secondary: hsl(227deg 92% 65%) !default;
$dark-color-text: hsl(210deg 14% 80%) !default;
$default-dark-colors: () !default;
$default-dark-colors: () !default;
$default-dark-colors: map.deep-merge(
(
'alert': (
'danger': hsl(0deg 80% 62%),
'info': hsl(195deg 90% 55%),
'success': hsl(150deg 70% 45%),
'warning': hsl(48deg 89% 60%),
),
'base': (
'background': $dark-color-background,
'blockquote-border': $dark-color-primary,
'border': $dark-color-border,
'border-strong': hsl(0deg 0% 100% / 22%),
'code-background': hsl(207deg 64% 16%),
'code-foreground': $dark-color-heading,
'focus-ring': $dark-color-primary,
'heading': $dark-color-heading,
'kbd-border': hsl(0deg 0% 100% / 18%),
'link': $dark-color-primary,
'link-hover': color.scale($dark-color-primary, $lightness: 25%),
'mark-background': hsl(50deg 100% 80%),
'mark-foreground': $dark-color-background,
'marker': $dark-color-primary,
'muted': hsl(210deg 10% 62%),
'overlay': hsl(0deg 0% 0% / 70%),
'pre-background': hsl(207deg 64% 12%),
'pre-foreground': $dark-color-heading,
'primary': $dark-color-primary,
'secondary': $dark-color-secondary,
'strong': $dark-color-heading,
'surface': color.scale($dark-color-background, $lightness: 4%),
'surface-muted': color.scale($dark-color-background, $lightness: 2%),
'text': $dark-color-text,
),
'btn': (
'danger-background': hsl(0deg 70% 55%),
'danger-background-hover': hsl(0deg 70% 62%),
'danger-foreground': hsl(0deg 0% 100%),
'primary-background': hsl(261deg 52% 59%),
'primary-background-hover': hsl(261deg 52% 65%),
'primary-foreground': hsl(0deg 0% 100%),
'primary-shadow': hsl(261deg 52% 25%),
'secondary-background': hsl(227deg 80% 58%),
'secondary-background-hover': hsl(227deg 80% 64%),
'secondary-foreground': hsl(0deg 0% 100%),
'secondary-shadow': hsl(227deg 80% 25%),
),
'form': (
'background': color.scale($dark-color-background, $lightness: 5%),
'background-disabled': $dark-color-background,
'border': hsl(0deg 0% 100% / 16%),
'border-disabled': $dark-color-border,
'border-focus': $dark-color-primary,
'check-background': $dark-color-primary,
'check-focus-ring': $dark-color-primary,
'check-foreground': $dark-color-background,
'group-label-background': color.scale($dark-color-background, $lightness: 3%),
'group-label-foreground': $dark-color-text,
'invalid': hsl(0deg 80% 62%),
'invalid-focus-ring': hsl(0deg 80% 62% / 25%),
'label': $dark-color-heading,
'legend': $dark-color-heading,
'placeholder': hsl(0deg 0% 60%),
'progress-background': hsl(0deg 0% 100% / 12%),
'progress-foreground': $dark-color-primary,
'range-thumb-background': $dark-color-primary,
'range-thumb-focus-ring': $dark-color-primary,
'range-track-background': hsl(0deg 0% 100% / 12%),
'required': hsl(0deg 80% 62%),
'ring-focus': hsl(261deg 54% 70% / 25%),
'select-foreground': hsl(0deg 0% 100%),
'switch-background': $dark-color-primary,
'switch-focus-ring': $dark-color-primary,
'switch-foreground': $dark-color-background,
'text': $dark-color-text,
'valid': hsl(150deg 70% 45%),
'valid-focus-ring': hsl(150deg 70% 45% / 25%),
),
'scrollbar': (
'thumb-background': hsl(0deg 0% 100% / 15%),
'thumb-background-hover': hsl(0deg 0% 100% / 25%),
'track-background': hsl(0deg 0% 100% / 5%),
),
'selection': (
'background': $dark-color-primary,
'foreground': $dark-color-background,
),
'table': (
'border': $dark-color-border,
'caption': $dark-color-text,
'heading': $dark-color-heading,
'hover': hsl(0deg 0% 100% / 5%),
'stripe': hsl(0deg 0% 100% / 2.5%),
'text': $dark-color-text,
),
),
$default-dark-colors
);In this example, you can see how to generate a component (an alert) from the alert colors with Sass’s @each and map.get.
@use 'sass:color';
@use 'sass:map';
@use 'sprucecss/scss/spruce' as *;
.alert {
border: 1px solid;
border-inline-start-width: 0.35rem;
border-radius: radius('lg');
padding: 0.65em 1em;
@each $name, $value in map.get($colors, 'alert') {
&--#{$name} {
background-color: color-tint($name, 90%, 'alert');
color: color.scale($value, $lightness: -30%);
}
}
}

