Variables

Spruce CSS has several Sass variables and maps that it can generate into CSS custom properties.

Our central concept is not to expose a CSS custom property that isn’t needed for later customization, but you can opt in to the full custom property mode.

By default, Spruce only generates CSS variables for the colors and the transition settings. If you set css-custom-properties to true, it also generates them for the typography, display, shadows, buttons, form elements, tables and more, with style hooks.

@use 'sprucecss/scss/spruce' with (
  $settings: (
    'css-custom-properties': true,
  ),
);

How to Overwrite

Every map below is merged with your values, so you only pass the keys you change, at the first @use of Spruce:

@use 'sprucecss/scss/spruce' with (
  $btn: (
    'border-radius': 0,
    'font-weight': 700,
  ),
  $shadows: (
    'md': 0 6px 20px hsl(0deg 0% 0% / 12%),
  ),
);

The code blocks show the source of the installed version, with the default values. Read more about the difference between Sass and CSS variables, and see the colors, settings and styles on their own pages.

View Source on GitHub

Breakpoints

The breakpoints used by the breakpoint, breakpoint-down, breakpoint-between and container-query mixins.

View on GitHub

$breakpoints: () !default;
$breakpoints: map.merge(
  (
    'xs': 32em,
    'sm': 48em,
    'md': 64em,
    'lg': 80em,
    'xl': 90em,
    'xxl': 110em,
  ),
  $breakpoints
);

Buttons

The button maps. The $btn-sm and $btn-lg maps only overwrite what differs from $btn. New in v3: box-shadow, disabled-opacity, letter-spacing, line-height.

View on GitHub

$btn: () !default;
$btn: map.merge(
  (
    'border-radius': map.get($display, 'border-radius-sm'),
    'border-width': 1px,
    'box-shadow': null,
    'disabled-opacity': 0.5,
    'focus-ring-box-shadow-type': outside,
    'focus-ring-offset': 2px,
    'focus-ring-size': 2px,
    'focus-ring-type': outline,
    'font-family': null,
    'font-size': map.get($typography, 'font-size-base'),
    'font-style': null,
    'font-weight': 500,
    'gap': map.get($spacers, 'xs'),
    'icon-padding': 0.75em,
    'icon-size': 1em,
    'letter-spacing': null,
    'line-height': 1,
    'padding': 0.75em 1em,
    'shadow-size': 0.25rem,
    'text-transform': null,
  ),
  $btn
);

$btn-lg: () !default;
$btn-lg: map.merge(
  (
    'font-size': 1.15rem,
    'gap': map.get($spacers, 'xs'),
    'icon-padding': 0.9em,
    'padding': 0.9em 1.15em,
  ),
  $btn-lg
);

$btn-sm: () !default;
$btn-sm: map.merge(
  (
    'font-size': 0.8rem,
    'gap': map.get($spacers, 'xxs'),
    'icon-padding': 0.5em,
    'icon-size': 0.8rem,
    'padding': 0.5em 0.75em,
  ),
  $btn-sm
);

Character Escaping

The characters the svg-escape() function escapes when an SVG is used as a data:image.

View on GitHub

/// Characters to escape using SVG as data:image.
$escaping-characters: (
  ('<', '%3c'),
  ('>', '%3e'),
  ('#', '%23'),
  ('(', '%28'),
  (')', '%29'),
) !default;

Details

New in v3. The details element options.

View on GitHub

$details: () !default;
$details: map.merge(
  (
    'border-radius': map.get($display, 'border-radius-sm'),
    'border-width': 1px,
    'gap': map.get($spacers, 'xs'),
    'icon-size': 1em,
    'padding': map.get($spacers, 's'),
    'summary-font-weight': 600,
  ),
  $details
);

Dialog

New in v3. The dialog element options.

View on GitHub

$dialog: () !default;
$dialog: map.merge(
  (
    'backdrop-filter': none,
    'border-radius': map.get($display, 'border-radius-lg'),
    'border-width': 1px,
    'inline-size': 40rem,
    'padding': map.get($spacers, 'm'),
  ),
  $dialog
);

Display

Shared shape tokens. New in v3: border-radius-md, border-radius-pill and border-width-base. Get them with radius() or config(…, $display).

View on GitHub

$display: () !default;
$display: map.merge(
  (
    'border-radius-lg': 0.925rem,
    'border-radius-md': 0.675rem,
    'border-radius-pill': 62.5rem,
    'border-radius-sm': 0.425rem,
    'border-width-base': 1px,
  ),
  $display
);

Divider

New in v3. The hr element options.

View on GitHub

$divider: () !default;
$divider: map.merge(
  (
    'border-style': solid,
    'border-width': 1px,
    'margin-block': null,
  ),
  $divider
);

Form Check

Checkboxes and radio buttons.

View on GitHub

$form-check: () !default;
$form-check: map.merge(
  (
    'border-radius': map.get($display, 'border-radius-sm'),
    'border-width': 1px,
    'focus-ring-box-shadow-type': outside,
    'focus-ring-offset': 2px,
    'focus-ring-size': 2px,
    'focus-ring-type': outline,
    'font-size': 1.125rem,
    'font-weight': map.get($form-label, 'font-weight'),
    'line-height': map.get($typography, 'line-height-md'),
    'margin-block': 0.1em,
    'vertical-alignment': center,
  ),
  $form-check
);

$form-check-lg: () !default;
$form-check-lg: map.merge(
  (
    'font-size': map.get($typography, 'font-size-lg'),
  ),
  $form-check-lg
);

$form-check-sm: () !default;
$form-check-sm: map.merge(
  (
    'font-size': map.get($typography, 'font-size-base'),
  ),
  $form-check-sm
);

Form Control

Inputs, selects and textareas, with the size and color input maps. New in v3: box-shadow, letter-spacing.

View on GitHub

$form-control: () !default;
$form-control: map.merge(
  (
    'border-radius': map.get($display, 'border-radius-sm'),
    'border-width': 1px,
    'box-shadow': null,
    'focus-ring-box-shadow-type': outside,
    'focus-ring-offset': 2px,
    'focus-ring-size': 0.25rem,
    'focus-ring-type': box-shadow,
    'font-family': null,
    'font-size': map.get($typography, 'font-size-base'),
    'font-weight': null,
    'letter-spacing': null,
    'line-height': 1.5,
    'padding': 0.5em 0.75em,
    'textarea-block-size': 6rem,
  ),
  $form-control
);

$form-control-lg: () !default;
$form-control-lg: map.merge(
  (
    'font-size': map.get($typography, 'font-size-lg'),
    'padding': 0.65em 1em,
  ),
  $form-control-lg
);

$form-control-sm: () !default;
$form-control-sm: map.merge(
  (
    'border-radius': 0.35em,
    'font-size': map.get($typography, 'font-size-sm'),
    'padding': 0.25em 0.75em,
  ),
  $form-control-sm
);

$form-control-color: () !default;
$form-control-color: map.merge(
  (
    'aspect-ratio': 1,
    'block-size': 100%,
    'inline-size': 2.625rem,
    'padding': 0.5em,
  ),
  $form-control-color
);

$form-control-color-lg: () !default;
$form-control-color-lg: map.merge(
  (
    'aspect-ratio': 1,
    'block-size': 100%,
    'inline-size': 3.204rem,
    'padding': 0.5em,
  ),
  $form-control-color-lg
);

$form-control-color-sm: () !default;
$form-control-color-sm: map.merge(
  (
    'aspect-ratio': 1,
    'block-size': 100%,
    'inline-size': 1.925rem,
    'padding': 0.25em,
  ),
  $form-control-color-sm
);

Form Description

The help text under the controls.

View on GitHub

$form-description: () !default;
$form-description: map.merge(
  (
    'font-size': 1em,
    'font-style': null,
    'font-weight': 400,
  ),
  $form-description
);

Form Fieldset

Fieldsets and legends.

View on GitHub

$form-fieldset: () !default;
$form-fieldset: map.merge(
  (
    'layout-gap': map.get($spacers, 's'),
    'legend-font-family': null,
    'legend-font-size': clamp(#{map.get($font-sizes, 'h5')}, 5vw, #{map.get($font-sizes, 'h4')}),
    'legend-font-weight': 700,
  ),
  $form-fieldset
);

Form File

The file input button (background is the name of a button color variant).

View on GitHub

$form-file: () !default;
$form-file: map.merge(
  (
    'background': 'primary',
  ),
  $form-file
);

Form Group

The form group layouts.

View on GitHub

$form-group: () !default;
$form-group: map.merge(
  (
    'gap': map.get($spacers, 'xs'),
  ),
  $form-group
);

$form-group-check: () !default;
$form-group-check: map.merge(
  (
    'gap': map.get($spacers, 's'),
  ),
  $form-group-check
);

$form-group-row: () !default;
$form-group-row: map.merge(
  (
    'container-inline-size': 38rem,
    'gap': map.get($spacers, 'xxs') map.get($spacers, 's'),
    'label-inline-size': 10rem,
    'vertical-alignment': center,
  ),
  $form-group-row
);

Form Icon

The SVG icons of the form elements. #COLOR# is replaced with the related color.

View on GitHub

$form-icon: () !default;
$form-icon: map.merge(
  (
    'checkbox-indeterminate': '<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M19.5,11.1c-0,-0.331 -0.269,-0.6 -0.6,-0.6l-13.8,0c-0.331,0 -0.6,0.269 -0.6,0.6l0,1.8c0,0.331 0.269,0.6 0.6,0.6l13.8,0c0.331,0 0.6,-0.269 0.6,-0.6l-0,-1.8Z" style="fill:#COLOR#;"/></svg>',
    'checkbox': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M9.525,13.777l-2.411,-2.412c-0.234,-0.233 -0.613,-0.233 -0.846,0l-1.27,1.27c-0.233,0.233 -0.233,0.612 0,0.846l4.104,4.103c0.116,0.117 0.269,0.175 0.422,0.175l0.003,0c0.152,0 0.305,-0.058 0.421,-0.175l9.054,-9.053c0.233,-0.234 0.233,-0.613 -0,-0.846l-1.27,-1.269c-0.233,-0.234 -0.612,-0.234 -0.846,-0l-7.361,7.361Z" style="fill:#COLOR#;"/></svg>',
    'invalid': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M12,2c5.519,0 10,4.481 10,10c0,5.519 -4.481,10 -10,10c-5.519,0 -10,-4.481 -10,-10c0,-5.519 4.481,-10 10,-10Zm0,1.667c4.599,-0 8.333,3.734 8.333,8.333c0,4.599 -3.734,8.333 -8.333,8.333c-4.599,0 -8.333,-3.734 -8.333,-8.333c-0,-4.599 3.734,-8.333 8.333,-8.333Zm0.813,11.649c-0,-0.081 -0.065,-0.146 -0.146,-0.146l-1.334,0c-0.081,0 -0.146,0.065 -0.146,0.146l0,1.169c0,0.08 0.065,0.146 0.146,0.146l1.334,-0c0.081,-0 0.146,-0.066 0.146,-0.146l-0,-1.169Zm-0,-7.784c-0,-0.09 -0.073,-0.163 -0.163,-0.163l-1.3,0c-0.09,0 -0.163,0.073 -0.163,0.163l0,6.351c0,0.09 0.073,0.163 0.163,0.163l1.3,-0c0.09,-0 0.163,-0.073 0.163,-0.163l-0,-6.351Z" style="fill:#COLOR#;"/></svg>',
    'radio': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="6" style="fill:#COLOR#;"/></svg>',
    'select': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12,12.507l-3.816,-3.815c-0.171,-0.172 -0.45,-0.172 -0.622,-0l-0.933,0.933c-0.172,0.172 -0.172,0.451 0,0.623l5.06,5.06c0.172,0.172 0.45,0.172 0.622,0l5.06,-5.06c0.172,-0.172 0.172,-0.451 -0,-0.623l-0.933,-0.933c-0.172,-0.172 -0.451,-0.172 -0.622,-0l-3.816,3.815Z" style="fill:#COLOR#;"/></svg>',
    'switch': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="8.5" style="fill:#COLOR#;"/></svg>',
    'valid': '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M12,2c5.519,0 10,4.481 10,10c0,5.519 -4.481,10 -10,10c-5.519,0 -10,-4.481 -10,-10c0,-5.519 4.481,-10 10,-10Zm0,1.667c4.599,-0 8.333,3.734 8.333,8.333c0,4.599 -3.734,8.333 -8.333,8.333c-4.599,0 -8.333,-3.734 -8.333,-8.333c-0,-4.599 3.734,-8.333 8.333,-8.333Zm-1.476,10.182l-2.984,-2.984c-0.065,-0.065 -0.17,-0.065 -0.235,0l-0.943,0.943c-0.065,0.065 -0.065,0.171 -0,0.236l4.043,4.042c0.033,0.033 0.076,0.05 0.119,0.049c0.044,0.001 0.087,-0.016 0.12,-0.049l6.994,-6.994c0.065,-0.065 0.065,-0.17 0,-0.235l-0.943,-0.943c-0.065,-0.065 -0.17,-0.065 -0.235,-0l-5.936,5.935Z" style="fill:#COLOR#;"/></svg>',
  ),
  $form-icon
);

Form Label

New in v3: line-height, required-content, required-gap.

View on GitHub

$form-label: () !default;
$form-label: map.merge(
  (
    'font-family': null,
    'font-size': null,
    'font-style': null,
    'font-weight': null,
    'line-height': null,
    'required-content': '*',
    'required-gap': 0.25em,
    'text-align': start,
    'text-transform': null,
  ),
  $form-label
);

Form Range

New in v3: the $form-range-sm and $form-range-lg maps.

View on GitHub

$form-range: () !default;
$form-range: map.merge(
  (
    'focus-ring-box-shadow-type': outside,
    'focus-ring-offset': 2px,
    'focus-ring-size': 2px,
    'focus-ring-type': outline,
    'thumb-block-size': 1rem,
    'thumb-border-radius': 0.5rem,
    'thumb-inline-size': 1rem,
    'track-block-size': 0.25rem,
    'track-border-radius': 0.15rem,
  ),
  $form-range
);

$form-range-lg: () !default;
$form-range-lg: map.merge(
  (
    'thumb-block-size': 1.25rem,
    'thumb-border-radius': 0.625rem,
    'thumb-inline-size': 1.25rem,
    'track-block-size': 0.375rem,
    'track-border-radius': 0.1875rem,
  ),
  $form-range-lg
);

$form-range-sm: () !default;
$form-range-sm: map.merge(
  (
    'thumb-block-size': 0.75rem,
    'thumb-border-radius': 0.375rem,
    'thumb-inline-size': 0.75rem,
    'track-block-size': 0.1875rem,
    'track-border-radius': 0.1rem,
  ),
  $form-range-sm
);

Form Row

The .form-row--mixed layout.

View on GitHub

$form-row: () !default;
$form-row: map.merge(
  (
    'inline-size': 20ch,
  ),
  $form-row
);

Form Select

The select arrow and the validation icon placement.

View on GitHub

$form-select: () !default;
$form-select: map.merge(
  (
    'icon-inline-size': 1.25em,
    'icon-right-offset': 0.5em,
    'padding-inline-end': calc(0.75em + 1.25em),
  ),
  $form-select
);

Form Switch

Switches.

View on GitHub

$form-switch: () !default;
$form-switch: map.merge(
  (
    'border-width': 1px,
    'font-size': 1.125rem,
    'font-weight': map.get($form-label, 'font-weight'),
    'line-height': map.get($typography, 'line-height-md'),
    'margin-block': 0.15em,
    'vertical-alignment': center,
  ),
  $form-switch
);

$form-switch-lg: () !default;
$form-switch-lg: map.merge(
  (
    'font-size': map.get($typography, 'font-size-lead'),
  ),
  $form-switch-lg
);


$form-switch-sm: () !default;
$form-switch-sm: map.merge(
  (
    'font-size': map.get($typography, 'font-size-base'),
  ),
  $form-switch-sm
);

Generators

Turn the style generation on or off by part. See the Generators page.

View on GitHub

$generators: () !default;
$generators: map.deep-merge(
  (
    'content': (
      'accessibility': true,
      'dark-mode': false,
      'default': true,
      'details': true,
      'dialog': true,
      'display': true,
      'divider': true,
      'layout': true,
      'media': true,
      'normalize': true,
      'print': true,
      'progress': true,
      'root': true,
      'styles': false,
      'table': true,
      'typography': true,
      'utilities': true,
    ),
    'form': (
      'btn': true,
      'btn-group': true,
      'file-btn': true,
      'form-check': true,
      'form-control': true,
      'form-description': true,
      'form-feedback': true,
      'form-fieldset': true,
      'form-group-label': true,
      'form-group': true,
      'form-label': true,
      'form-range': true,
      'form-row': true,
      'form-switch': true,
    ),
  ),
  $generators
);

Layout

The default container width.

View on GitHub

$layout: () !default;
$layout: map.merge(
  (
    'container-inline-size': 84rem,
  ),
  $layout
);

Media

New in v3. Image and figure options.

View on GitHub

$media: () !default;
$media: map.merge(
  (
    'figcaption-font-size': null,
    'figcaption-font-style': null,
    'figcaption-margin-block-start': map.get($spacers, 'xs'),
    'figcaption-text-align': center,
    'img-border-radius': null,
  ),
  $media
);

Print

The print styles.

View on GitHub

$print: () !default;
$print: map.merge(
  (
    'page-margin': 2cm,
    'hidden-elements': 'header, footer, aside, nav, form, iframe, [class^="aspect-ratio"]',
  ),
  $print
);

Progress

New in v3. The progress and meter elements.

View on GitHub

$progress: () !default;
$progress: map.merge(
  (
    'block-size': 0.5rem,
    'border-radius': map.get($display, 'border-radius-pill'),
  ),
  $progress
);

Shadows

New in v3. The shadow scale. Get them with shadow(); in custom property mode they are output as --spruce-shadow-*.

View on GitHub

$shadows: () !default;
$shadows: map.merge(
  (
    'xs': 0 1px 2px hsl(0deg 0% 0% / 5%),
    'sm': (0 1px 3px hsl(0deg 0% 0% / 10%), 0 1px 2px -1px hsl(0deg 0% 0% / 10%)),
    'md': (0 4px 6px -1px hsl(0deg 0% 0% / 10%), 0 2px 4px -2px hsl(0deg 0% 0% / 10%)),
    'lg': (0 10px 15px -3px hsl(0deg 0% 0% / 10%), 0 4px 6px -4px hsl(0deg 0% 0% / 10%)),
    'xl': (0 20px 25px -5px hsl(0deg 0% 0% / 10%), 0 8px 10px -6px hsl(0deg 0% 0% / 10%)),
    'inner': inset 0 2px 4px 0 hsl(0deg 0% 0% / 5%),
  ),
  $shadows
);

Spacers

The spacers are generated from the $spacer value. Get them with spacer() and spacer-clamp().

View on GitHub

$spacer: 1rem !default;

$spacers: () !default;
$spacers: map.merge(
  (
    'xxs': $spacer * 0.25,
    'xs': $spacer * 0.5,
    's': $spacer,
    'm': $spacer * 1.5,
    'l': $spacer * 3,
    'xl': $spacer * 4.5,
    'xxl': $spacer * 7,
    'xxxl': $spacer * 10,
  ),
  $spacers
);

Tables

New in v3: border-width, caption-text-align, the heading-* keys and vertical-align.

View on GitHub

$table: () !default;
$table: map.merge(
  (
    'border-width': 1px,
    'caption-font-size': null,
    'caption-font-style': null,
    'caption-font-weight': null,
    'caption-text-align': null,
    'heading-font-size': null,
    'heading-font-weight': null,
    'heading-letter-spacing': null,
    'heading-text-transform': null,
    'line-height': map.get($typography, 'line-height-md'),
    'padding': map.get($spacers, 's'),
    'responsive-inline-size': 40rem,
    'stripe': odd,
    'vertical-align': null,
  ),
  $table
);

$table-sm: () !default;
$table-sm: map.merge(
  (
    'padding': map.get($spacers, 'xs'),
  ),
  $table-sm
);

Transition

The default transition settings. In custom property mode, the duration is 0 for users who prefer reduced motion.

View on GitHub

$transition: () !default;
$transition: map.merge(
  (
    'duration': 0.15s,
    'timing-function': ease-in-out,
  ),
  $transition
);

Typography

The typography tokens and the heading scale. Get the sizes with font-size(). New in v3: blockquote-*, font-family-mono, font-size-code, font-weight-strong, letter-spacing-base, link-*, list-gap, pre-*, text-transform-heading, text-wrap-base, text-wrap-heading.

View on GitHub

$typography: () !default;
$typography: map.merge(
  (
    'blockquote-border-width': 0.5rem,
    'blockquote-font-size': null,
    'blockquote-font-style': null,
    'border-radius': map.get($display, 'border-radius-sm'),
    'font-family-base': #{Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif},
    'font-family-cursive': #{ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace},
    'font-family-heading': #{Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif},
    'font-family-mono': #{ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace},
    'font-size-base': 1rem,
    'font-size-code': null,
    'font-size-lead': clamp(1.15rem, 2vw, 1.35rem),
    'font-size-lg': 1.125rem,
    'font-size-ratio': 1.25,
    'font-size-sm': 0.875rem,
    'font-weight-base': null,
    'font-weight-heading': 700,
    'font-weight-strong': null,
    'inline-padding': 0.1em 0.3em,
    'letter-spacing-base': null,
    'letter-spacing-heading': null,
    'line-height-base': 1.5,
    'line-height-heading': calc(2px + 2ex + 2px),
    'line-height-lg': 1.8,
    'line-height-md': 1.5,
    'line-height-sm': 1.2,
    'link-font-weight': null,
    'link-text-decoration': underline,
    'link-text-decoration-hover': null,
    'link-text-decoration-thickness': null,
    'link-text-underline-offset': null,
    'list-gap': map.get($spacers, 'xxs'),
    'pre-border-radius': map.get($display, 'border-radius-sm'),
    'pre-padding': map.get($spacers, 's'),
    'text-transform-heading': null,
    'text-wrap-base': null,
    'text-wrap-heading': balance,
  ),
  $typography
);

$font-sizes: () !default;
$font-sizes: map.merge(
  (
    'h1': math.pow(map.get($typography, 'font-size-ratio'), 4) * map.get($typography, 'font-size-base'),
    'h2': math.pow(map.get($typography, 'font-size-ratio'), 3) * map.get($typography, 'font-size-base'),
    'h3': math.pow(map.get($typography, 'font-size-ratio'), 2) * map.get($typography, 'font-size-base'),
    'h4': math.pow(map.get($typography, 'font-size-ratio'), 1) * map.get($typography, 'font-size-base'),
    'h5': map.get($typography, 'font-size-base'),
    'h6': map.get($typography, 'font-size-base'),
  ),
  $font-sizes
);

Z-Index

New in v3. The layer scale. Get the values with z-index().

View on GitHub

$z-indexes: () !default;
$z-indexes: map.merge(
  (
    'dropdown': 1000,
    'sticky': 1020,
    'fixed': 1030,
    'overlay': 1040,
    'modal': 1050,
    'popover': 1060,
    'toast': 1070,
    'tooltip': 1080,
  ),
  $z-indexes
);

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.