Sass
Utilize Chassis source Sass files to take advantage of variables, maps, mixins, and functions to customize and extend the framework.
Chassis ships its source on the Sass module system (@use / @forward). Pull individual modules in, override their defaults with @use … with (…), and compose your own bundle without touching framework source.
File structure
Whenever possible, avoid modifying Chassis source files. Create your own stylesheet that uses Chassis instead. With a package manager you'll have something like:
your-project/
├── scss/
│ └── custom.scss
└── node_modules/
│ └── @chassis-ui/
│ └── css/
│ ├── js/
│ └── scss/
└── index.htmlIf you downloaded the source files manually, mirror that layout — keep Chassis's source separate from your own.
Importing
In your custom.scss, pull Chassis in with @use. Two options are available: load all of Chassis, or pick the parts you need. Prefer the latter, though be aware components have dependencies across the framework.
// custom.scss
// Option A: Load all of Chassis CSS
@use "@chassis-ui/css/scss/chassis";
// Then add custom code here// custom.scss
// Option B: Load parts of Chassis CSS
// 1. Functions first (so you can manipulate colors, SVGs, calc, etc)
@use "@chassis-ui/css/scss/functions" as *;
// 2. Variables — configure here with `@use ... with (...)` if needed
@use "@chassis-ui/css/scss/config/defaults" as *;
// 3. Maps and mixins
@use "@chassis-ui/css/scss/maps" as *;
@use "@chassis-ui/css/scss/mixins" as *;
// 4. CSS custom property layer
@use "@chassis-ui/css/scss/root";
// 5. Optional parts; see chassis.scss for the full list
@use "@chassis-ui/css/scss/reboot";
@use "@chassis-ui/css/scss/type";
@use "@chassis-ui/css/scss/grid";
@use "@chassis-ui/css/scss/utilities";
@use "@chassis-ui/css/scss/helpers";
// 6. Utilities API last — generates classes from the `$utilities` map
@use "@chassis-ui/css/scss/utilities-api";With that setup in place, you can override variables (see below) and add Chassis modules as needed. Use the @use stack in chassis.scss as a starting reference.
Importing Chassis with bare specifiers (@chassis-ui/css/scss/…) requires your Sass setup to resolve node_modules. Vite, Webpack, and Astro do this automatically. For the Sass CLI, add --load-path node_modules/.
Compiling
You need a Sass compiler. Sass ships as a CLI package, but you can also compile with Vite, Webpack, Parcel, Gulp, or any other build tool.
# Install Sass
npm install -D sass
# Watch your custom Sass for changes and compile it to CSS
npx sass --watch --load-path node_modules/ ./scss/custom.scss ./css/custom.cssIncluding
Once your CSS is compiled, include it in your HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Custom Chassis CSS</title>
<link href="/css/custom.css" rel="stylesheet">
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>Variable defaults
Every Chassis Sass variable is declared with !default. Under the module system, you configure module defaults at the @use call site:
@use "@chassis-ui/css/scss/config/defaults" with (
$bg-color: #000,
$fg-color: #111,
);
@use "@chassis-ui/css/scss/chassis"; // picks up your configured defaultsYou can also override a variable from a more specific module — components, maps, etc.:
@use "@chassis-ui/css/scss/config/defaults" with (
$primary: #0074d9,
$danger: #ff4136,
);Sass loads each module exactly once, so the configured instance is shared by every downstream module that imports it. The with (…) call must appear before any module that transitively uses the target.
Some variables default to null. Those don't emit the property unless you override them.
The complete list lives in scss/config/_defaults.scss.
Get started with Chassis CSS via npm with our starter project! Head to the Sass & JS example template repository to see how to build and customize Chassis CSS in your own npm project. Includes Sass compiler, Autoprefixer, Stylelint, PurgeCSS, and Chassis CSS Icons.
Maps and loops
Chassis ships several Sass maps — key/value pairs that power families of related CSS (colors, breakpoints, etc.). Every map is !default, so you can override or extend them through @use ... with (...).
Modify a map
Most map entries are exposed as standalone variables ($primary, $danger, etc.). Configure those variables and the maps that reference them update automatically:
@use "@chassis-ui/css/scss/config/defaults" with (
$primary: #0074d9,
$danger: #ff4136,
);Add to a map
To extend a map (rather than replacing it), configure the map itself:
@use "sass:map";
@use "@chassis-ui/css/scss/maps" as chassis-maps;
@use "@chassis-ui/css/scss/maps" with (
$context-colors: map.merge(
chassis-maps.$context-colors,
("custom": #900),
)
);Or merge in your own code after @use:
@use "sass:map";
@use "@chassis-ui/css/scss/maps" as maps;
$context-colors: map.merge(maps.$context-colors, ("custom": #900));Remove from a map
Use map.remove() in the with (…) configuration:
@use "sass:map";
@use "@chassis-ui/css/scss/maps" as chassis-maps;
@use "@chassis-ui/css/scss/maps" with (
$context-colors: map.remove(chassis-maps.$context-colors, "info", "light", "dark")
);Required keys
Chassis assumes certain keys exist in some maps. For example, the framework consumes primary, success, and danger from $context-colors for links, buttons, and form states. Changing their values is safe; removing them may cause Sass compilation errors that you'll have to address in your own overrides.
Functions
Colors
Chassis uses oklch as its default color space. For the full picture, see Color System and Design Tokens.
The following functions in scss/functions/_color.scss are the primary tools for working with colors.
to-color()
Converts any Sass-compatible color to a rounded oklch() string. The framework uses it internally to normalize tokens before writing them to CSS custom properties.
/// Convert a color to an oklch() value with rounded channel values
/// Converts any Sass-supported color to the OKLCh color space, rounding lightness
/// to 2 decimal places, chroma to 3 decimal places, and hue to 2 decimal places.
/// Alpha is preserved when less than 1.
///
/// @param {Color} $color - Any Sass-compatible color value
/// @return {Color} - An oklch() color with rounded L, C, H channels and preserved alpha
/// @example
/// to-color(#ff0000) => oklch(0.63 0.258 29.23)
/// to-color(rgba(#ff0000, 0.5)) => oklch(0.63 0.258 29.23 / 0.5)
@function to-color($color) {
$oklch: color.to-space($color, oklch);
// Extract and round channels
$l: math.div(math.round(color.channel($oklch, "lightness", oklch) * 100), 100);
$c: math.div(math.round(color.channel($oklch, "chroma", oklch) * 1000), 1000);
$a: color.alpha($color);
// Achromatic colors (chroma rounds to 0) have no meaningful hue;
// skip math on the 'none' keyword to avoid calc(NaN * 1deg) output.
$h: none;
@if $c != 0 {
$h: math.div(math.round(color.channel($oklch, "hue", oklch) * 100), 100);
}
// Use string interpolation so the CSS '/' is a channel separator, not Sass division.
@if $a < 1 {
@return #{"oklch(#{$l} #{$c} #{$h} / #{$a})"};
}
@return #{"oklch(#{$l} #{$c} #{$h})"};
}$converted: to-color(#0d6efd);
// => oklch(0.55 0.215 262.88)
$with-alpha: to-color(rgba(#0d6efd, 0.5));
// => oklch(0.55 0.215 262.88 / 0.5)Because to-color() outputs a string in the oklch space, it works in any browser with oklch support. To use a different color space as your default, swap calls to to-color() for a function that emits your preferred space (color(display-p3 …), lch(…), etc.).
to-opacity()
Wraps a color or CSS variable in a CSS relative color expression that applies an opacity value via the oklch channel syntax. This is how Chassis applies contextual opacity to tokens at runtime without generating separate color variants.
/// Generate a CSS relative color expression that applies an opacity variable to a CSS custom property
/// Uses the CSS relative color syntax to derive an oklch color from an existing
/// CSS variable and apply a separate opacity (alpha) CSS variable to it.
///
/// @param {Color|String} $color - Color value or `var()` expression used as the relative color origin
/// @param {Number|String} $opacity - Alpha value (0–1) or `var()` expression for the opacity channel
/// @return {String} - Unquoted CSS relative color string: oklch(from {color} l c h / {opacity})
/// @example
/// to-opacity(var(--cx-primary), 0.5) => oklch(from var(--cx-primary) l c h / 0.5)
/// to-opacity($white, .15) => oklch(from #fff l c h / 0.15)
/// to-opacity(var(--cx-fg-main), var(--cx-fg-opacity, 1)) => oklch(from var(--cx-fg-main) l c h / var(--cx-fg-opacity, 1))
@function to-opacity($color, $opacity) {
@return #{"oklch(from #{$color} l c h / #{$opacity})"};
}to-opacity(var(--cx-primary), 0.5)
// => oklch(from var(--cx-primary) l c h / 0.5)
to-opacity($white, .15)
// => oklch(from #fff l c h / 0.15)
to-opacity(var(--cx-fg-main), var(--cx-fg-opacity, 1))
// => oklch(from var(--cx-fg-main) l c h / var(--cx-fg-opacity, 1))The from keyword and channel names (l c h) are specific to oklch. If you change the default color space, update the channel names accordingly.
opacity-var()
A shorthand variant of to-opacity() designed to work as a map-loop callback. It takes an unprefixed color identifier and an opacity component name and returns a relative color expression. The framework's --cx- prefix is added later by the PostCSS prefix step — the Sass output uses unprefixed variable names.
/// Generate a CSS relative color expression that applies a component opacity variable to a color token
/// Uses CSS relative color syntax to derive an oklch color from a CSS custom property and apply
/// a separate opacity CSS variable to it. Designed to work as a `map-loop` callback.
///
/// The framework's `--cx-` prefix is added later by the PostCSS prefix step;
/// the Sass output of this function uses unprefixed variable names.
///
/// @param {String} $identifier - Color CSS variable name without prefix (e.g., "primary", "fg-main")
/// @param {String} $target - Opacity variable component name without prefix (e.g., "fg", "bg", "icon")
/// @return {String} - Unquoted CSS relative color: oklch(from var(--{identifier}) l c h / var(--{target}-opacity, 1))
/// @example
/// opacity-var("primary", "bg") => oklch(from var(--primary) l c h / var(--bg-opacity, 1))
/// opacity-var("fg-main", "fg") => oklch(from var(--fg-main) l c h / var(--fg-opacity, 1))
@function opacity-var($identifier, $target) {
@return #{"oklch(from var(--#{$identifier}) l c h / var(--#{$target}-opacity, 1))"};
}opacity-var("primary", "bg")
// => oklch(from var(--primary) l c h / var(--bg-opacity, 1))
opacity-var("fg-main", "fg")
// => oklch(from var(--fg-main) l c h / var(--fg-opacity, 1))This function is used when generating utility classes and component styles that need to respect a runtime opacity variable.
Escape SVG
The escape-svg() function escapes <, >, and # for SVG background images. Data URIs passed to it must be quoted.
Add and Subtract
add() and subtract() wrap CSS calc() to dodge an annoying browser quirk: expressions like calc(10px - 0) are invalid in every browser even though they're mathematically fine. Use these wrappers when one operand might be a unitless 0.
$border-radius: .25rem;
$border-width: 0;
.element {
border-radius: calc($border-radius - $border-width); // invalid in browsers
}
.element {
border-radius: subtract($border-radius, $border-width); // outputs .25rem
}Mixins
The scss/mixins/ directory contains the mixins that power Chassis components; they're also useful in your own code.
Color schemes
A shorthand for the prefers-color-scheme media query with light and dark support. See the color modes documentation for the broader color-mode mixin.
/// Wrap `@content` in a `@media (prefers-color-scheme: ...)` query.
///
/// @param {String} $name - Color scheme name (`light` or `dark`)
/// @content Styles to apply under this color scheme preference
@mixin color-scheme($name) {
@media (prefers-color-scheme: #{$name}) {
@content;
}
}.custom-element {
@include color-scheme(light) {
// light mode styles
}
@include color-scheme(dark) {
// dark mode styles
}
}Token sources
Chassis loads its design tokens through a single override point — scss/config/_token-source.scss. The framework's scss/config/_vendor.scss re-exports from it, so swapping the entire token feed is a one-line change in a file you control.
By default _token-source.scss forwards from @chassis-ui/tokens/dist/web/docs/chassis/main. Sass loads each module exactly once, so every downstream module — variables, maps, components — picks up whatever your override produces.
Switching token source
Create a _token-source.scss file in your project that forwards your preferred token set:
// my-project/sass-overrides/_token-source.scss
@forward "@chassis-ui/tokens/dist/web/docs/example/main";Then configure your build tool to search your override directory before the Chassis defaults. Because _token-source.scss sits at the root of the token dependency tree, all downstream modules pick up your token values automatically.
// vite.config.ts (or astro.config.ts)
import path from 'node:path'
export default {
css: {
preprocessorOptions: {
scss: {
loadPaths: [
path.resolve('./sass-overrides/'), // your _token-source.scss first
path.resolve('./node_modules/@chassis-ui/css/scss/config'), // framework fallback
path.resolve('./node_modules'), // bare package specifiers (@chassis-ui/tokens/…)
],
},
},
},
}Configuring variables
For partial overrides — adjusting a handful of token values — use @use ... with (…) against vendor. Sass loads the configured instance once and shares it with every downstream module:
// custom-entry.scss
@use "@chassis-ui/css/scss/config/vendor" with (
$cx-color-base-palette-light-primary-50: #ff385c,
$cx-color-base-palette-light-primary-40: #ff6a85,
);
// All subsequent chassis modules use the configured vendor instance
@use "@chassis-ui/css/scss/chassis";This is practical for a handful of targeted adjustments. For a full theme swap (~1,200 variable differences between token sets), the _token-source.scss override above is far more maintainable.
Overriding properties
To adjust visual output after the Sass compilation step, override the generated CSS custom properties:
:root {
--cx-primary: oklch(0.61 0.23 11.3);
}CSS custom property values are written by scss/_root.scss during compilation. Computed values — color maps, palette entries used inside component selectors — reflect the compiled token set and can't be changed post-compile. CSS custom property overrides are best suited for targeted, runtime adjustments.