Skip to main contentSkip to docs navigation

Utility API

The utility API is a Sass-based tool to generate utility classes.

Chassis CSS utilities are generated with our utility API and can be used to modify or extend our default set of utility classes via Sass. Our utility API is based on a series of Sass maps and functions for generating families of classes with various options. If you’re unfamiliar with Sass maps, read up on the official Sass docs to get started.

The $utilities map contains all our utilities and is later merged with your custom $utilities map, if present. The utility map contains a keyed list of utility groups which accept the following options:

OptionTypeDefault valueDescription
propertyRequiredName of the property. Can be a string, a space-separated list of strings (e.g., horizontal paddings or margins), or a map of property-to-default-value pairs (see Property-Value Mapping).
valuesRequiredList of values, or a map if you don’t want the class name to be the same as the value. If null is used as map key, class is not prepended to the class name.
selectorOptionalclassType of CSS selector in the generated CSS ruleset. Can be class, attr-starts, or attr-includes.
child-selectorOptionalnullA child/descendant selector appended to the utility’s selector, wrapped in :where() for zero specificity. Use to target children instead of the element itself.
classOptionalnullName of the generated class. If not provided and property is an array of strings, class will default to the first element of the property array. If not provided and property is a string, the values keys are used for the class names.
variablesOptionalnullList or map of CSS custom properties to generate within each utility class. When a list, each variable receives the utility value. When a map, the provided static values are used.
groupOptionalfalseBoolean. Requires property to be a map. Emits a single shared rule for all generated selectors containing the static (non-null) property values, then emits per-value rules with only the dynamic (null-valued) properties. Keeps repeated static declarations out of every per-value class.
stateOptionalnullList of pseudo-class variants (e.g., :hover or :focus) to generate as prefix-style classes.
responsiveOptionalfalseBoolean indicating if responsive classes should be generated.
importantOptionalfalseBoolean indicating if !important should be added to the utility’s CSS rules.
printOptionalfalseBoolean indicating if print classes need to be generated.
darkOptionalfalseBoolean indicating if dark: prefixed classes should be generated, scoped to @media (prefers-color-scheme: dark).
enabledOptionaltrueSet to false to suppress output for a utility while keeping its definition in the $utilities map.

API explained

All utility variables are added to the $utilities variable within our _utilities.scss stylesheet, which is a giant, nested Sass map. Each group of utility classes are generated from a block that looks something like this:

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

Which outputs the following:

CSS
.opacity-0 { opacity: 0; }
.opacity-25 { opacity: .25; }
.opacity-50 { opacity: .5; }
.opacity-75 { opacity: .75; }
.opacity-100 { opacity: 1; }

property

Required

The property key must be set for any utility, and it must contain a valid CSS property. This property is used in the generated utility’s ruleset. When the class key is omitted, it also serves as the default class name. Consider the text-decoration utility:

SCSS
$utilities: (
  "text-decoration": (
    property: text-decoration,
    values: none underline line-through
  )
);

Output:

CSS
.text-decoration-none { text-decoration: none; }
.text-decoration-underline { text-decoration: underline; }
.text-decoration-line-through { text-decoration: line-through; }

Property value mapping

The property key also accepts a map of CSS property names to default values. This lets a single utility class set multiple properties at once, and lets values override individual properties when the value is itself a map.

When a property's default value in the map is null, the utility value is used directly for that property. When a default value is set, it is used unless the utility's value map contains a matching key.

A common pattern is pairing a CSS custom property (which receives the utility value) with a regular property that references it:

SCSS
$utilities: (
  "bg-color": (
    property: (
      "--bg-color": null,
      "background-color": var(--bg-color)
    ),
    class: bg,
    values: (
      primary: var(--primary),
      danger:  var(--danger),
    )
  )
);

Output:

CSS
.bg-primary { --cx-bg-color: var(--cx-primary); background-color: var(--cx-bg-color); }
.bg-danger  { --cx-bg-color: var(--cx-danger);  background-color: var(--cx-bg-color); }

Source CSS variables are used without a --cx- prefix, which is added in compiled CSS with Bootstrap's PostCSS plugin.

You can also set per-value overrides. When values contains a nested map whose keys match properties in the property map, those values are used for the matching property:

SCSS
$utilities: (
  "text-size": (
    property: (
      "font-size":   1rem,
      "line-height": 1.5
    ),
    class: text,
    values: (
      "small": ("font-size": .875rem, "line-height": 1.25),
      "large": ("font-size": 1.25rem, "line-height": 1.75),
    )
  )
);

Output:

CSS
.text-small { font-size: .875rem; line-height: 1.25; }
.text-large { font-size: 1.25rem; line-height: 1.75; }

values

Required

Use the values key to specify which values for the specified property should be used in the generated class names and rules. Can be a list or map (set in the utilities or in a Sass variable).

As a list, like with text-decoration utilities:

SCSS
values: none underline line-through

As a map, like with opacity utilities:

SCSS
values: (
  0: 0,
  25: .25,
  50: .5,
  75: .75,
  100: 1,
)

As a Sass variable that sets the list or map, as in our position utilities:

SCSS
values: $position-values

selector

OptionDescription
No selectorDefaults to "class"
"class"Matches elements with a class that matches the specified value
"attr-starts"Matches elements with a class that starts with a string
"attr-includes"Matches elements with a class that includes a string

Use the selector option to change the CSS selector used in the generated CSS ruleset. The default option is to generate a class selector. When using an attribute selector—either attr-starts or attr-includesthe class option is required. We use these internally to simplify the construction of other utilities.

For attribute selectors, you’ll most likely want the attr-includes as the starting attribute selector in CSS applies to the entire string of classes in an attribute’s value. For example, [class^="name"] would not match class="example name".

As an example, to change from .ratio-* to [class*="ratio-"]:

SCSS
$utilities: (
  // Create an attribute selector utility
  "aspect-ratio-attr": (
    selector: "attr-includes",
    class: "ratio-",
    property: aspect-ratio,
    values: var(--ratio),
  ),
  // Create a CSS variable utility that matches the attribute selector utility
  "aspect-ratio": (
    property: --ratio,
    class: ratio,
    values: $aspect-ratios
  ),
);

Note that the --ratio custom property will automatically be prefixed to --cx-ratio in the compiled CSS via PostCSS.

Which outputs the following:

CSS
[class*="ratio-"] { aspect-ratio: var(--cx-ratio); }
.ratio-auto { --cx-ratio: auto; }
.ratio-1x1 { --cx-ratio: 1 / 1; }
.ratio-4x3 { --cx-ratio: 4 / 3; }
.ratio-16x9 { --cx-ratio: 16 / 9; }
.ratio-21x9 { --cx-ratio: 21 / 9; }

child-selector

Use the child-selector option to apply a CSS property to children of the element with the utility class rather than the element itself. The generated selector is wrapped in :where() for zero specificity, making it easy to override. This powers our built-in space-x/y and divide-x/y utilities.

For example, our space-x utility uses child-selector to add horizontal spacing between direct children:

SCSS
$utilities: (
  "space-x": (
    property: margin-inline-end,
    class: space-x,
    child-selector: "> :not(:last-child)",
    values: (
      1: .25rem,
      2: .5rem,
      3: 1rem,
    )
  )
);

Output:

CSS
:where(.space-x-1 > :not(:last-child)) { margin-inline-end: .25rem; }
:where(.space-x-2 > :not(:last-child)) { margin-inline-end: .5rem; }
:where(.space-x-3 > :not(:last-child)) { margin-inline-end: 1rem; }

The child-selector value can be any valid CSS selector. For example, you could create a striped row utility using :nth-child():

SCSS
$utilities: (
  "striped-bg": (
    property: background-color,
    class: striped,
    child-selector: "> :nth-child(odd)",
    values: (
      null: var(--cx-bg-even),
    )
  )
);

Output:

CSS
:where(.striped > :nth-child(odd)) { background-color: var(--cx-bg-even); }

Or target all direct children with > *:

SCSS
$utilities: (
  "child-rounded": (
    property: border-radius,
    class: child-rounded,
    child-selector: "> *",
    values: (
      null: var(--cx-border-radius),
      0: 0,
    )
  )
);

Output:

CSS
:where(.child-rounded > *) { border-radius: var(--cx-border-radius); }
:where(.child-rounded-0 > *) { border-radius: 0; }

class

Use the class option to change the class prefix used in the compiled CSS. For example, to change from .opacity-* to .o-*:

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    class: o,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

Output:

CSS
.o-0 { opacity: 0; }
.o-25 { opacity: .25; }
.o-50 { opacity: .5; }
.o-75 { opacity: .75; }
.o-100 { opacity: 1; }

If class: null, generates classes for each of the values keys:

SCSS
$utilities: (
  "visibility": (
    property: visibility,
    class: null,
    values: (
      visible: visible,
      invisible: hidden,
    )
  )
);

Output:

CSS
.visible { visibility: visible; }
.invisible { visibility: hidden; }

Variables

Use the variables option to generate CSS custom properties within each utility class’s ruleset. The value can be either a list or a map.

When variables is a list, each variable receives the current utility value:

SCSS
$utilities: (
  "link-opacity": (
    property: color,
    class: link,
    variables: link-color,
    values: (
      10: 10%,
      50: 50%,
      100: 100%,
    )
  ),
);

Output:

CSS
.link-10 { --cx-link-color: 10%; color: 10%; }
.link-50 { --cx-link-color: 50%; color: 50%; }
.link-100 { --cx-link-color: 100%; color: 100%; }

When variables is a map, the provided static values are used on every generated class:

SCSS
$utilities: (
  "link-underline": (
    property: text-decoration-color,
    class: link-underline,
    variables: (
      "link-underline-opacity": 1
    ),
    values: (...)
  )
);

Output:

CSS
.link-underline-primary {
  --cx-link-underline-opacity: 1;
  text-decoration-color: ...;
}

Group

Use group: true together with a property map to split the generated output into two sets of rules:

  1. One shared rule targeting every generated selector — containing the static (non-null) property values.
  2. One rule per value — containing only the dynamic (null-valued) properties.

This keeps repeated static declarations out of every per-value class, reducing output size and improving readability. group: true requires property to be a map; if it is a string or list the option is ignored with a warning.

A typical use case is a CSS custom property (set per value) paired with a regular property that references it (never changes):

SCSS
$utilities: (
  "text-context": (
    property: (
      "--fg-color": null,
      "color": var(--fg-color),
    ),
    class: text,
    group: true,
    values: (
      primary: var(--primary),
      danger:  var(--danger),
    )
  )
);

Output:

CSS
/* Shared rule — static properties written once */
.text-primary,
.text-danger {
  color: var(--cx-fg-color);
}

/* Per-value rules — only the dynamic custom property */
.text-primary { --cx-fg-color: var(--cx-primary); }
.text-danger  { --cx-fg-color: var(--cx-danger); }

Group with state

When state is also set, all state-variant selectors are included in the shared rule as well as in each per-value rule:

SCSS
$utilities: (
  "text-context": (
    property: (
      "--fg-color": null,
      "color": var(--fg-color),
    ),
    class: text,
    group: true,
    state: hover,
    values: (
      primary: var(--primary),
      danger:  var(--danger),
    )
  )
);

Output:

CSS
.text-primary,
.hover\:text-primary:hover,
.text-danger,
.hover\:text-danger:hover {
  color: var(--cx-fg-color);
}

.text-primary,
.hover\:text-primary:hover { --cx-fg-color: var(--cx-primary); }

.text-danger,
.hover\:text-danger:hover { --cx-fg-color: var(--cx-danger); }

Group with static variables

When variables is a map (static values), those variables are emitted in the shared rule rather than repeated on every per-value class:

SCSS
$utilities: (
  "text-context": (
    property: (
      "--fg-color": null,
      "color": var(--fg-color),
    ),
    class: text,
    group: true,
    variables: (
      fg-opacity: 1,
    ),
    values: (
      primary: var(--primary),
      danger:  var(--danger),
    )
  )
);

Output:

CSS
.text-primary,
.text-danger {
  color: var(--cx-fg-color);
  --cx-fg-opacity: 1;
}

.text-primary { --cx-fg-color: var(--cx-primary); }
.text-danger  { --cx-fg-color: var(--cx-danger); }

States

Use the state option to generate pseudo-class variations. Example pseudo-classes are :hover and :focus. When a list of states are provided, classnames are created for that pseudo-class with a prefix-style syntax matching the responsive prefix pattern. For example, to change opacity on hover, add state: hover and you’ll get .hover\:opacity:hover in your compiled CSS.

Need multiple pseudo-classes? Use a space-separated list of states: state: hover focus.

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    class: opacity,
    state: hover,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

Output:

CSS
.hover\:opacity-0:hover { opacity: 0; }
.hover\:opacity-25:hover { opacity: .25; }
.hover\:opacity-50:hover { opacity: .5; }
.hover\:opacity-75:hover { opacity: .75; }
.hover\:opacity-100:hover { opacity: 1; }

Responsive

Add the responsive boolean to generate responsive utilities (e.g., .md\:opacity-25) across all breakpoints.

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    responsive: true,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

Output:

CSS
.opacity-0 { opacity: 0; }
.opacity-25 { opacity: .25; }
.opacity-50 { opacity: .5; }
.opacity-75 { opacity: .75; }
.opacity-100 { opacity: 1; }

@media (min-width: 576px) {
  .sm\:opacity-0 { opacity: 0; }
  .sm\:opacity-25 { opacity: .25; }
  .sm\:opacity-50 { opacity: .5; }
  .sm\:opacity-75 { opacity: .75; }
  .sm\:opacity-100 { opacity: 1; }
}

@media (min-width: 768px) {
  .md\:opacity-0 { opacity: 0; }
  .md\:opacity-25 { opacity: .25; }
  .md\:opacity-50 { opacity: .5; }
  .md\:opacity-75 { opacity: .75; }
  .md\:opacity-100 { opacity: 1; }
}

@media (min-width: 1024px) {
  .lg\:opacity-0 { opacity: 0; }
  .lg\:opacity-25 { opacity: .25; }
  .lg\:opacity-50 { opacity: .5; }
  .lg\:opacity-75 { opacity: .75; }
  .lg\:opacity-100 { opacity: 1; }
}

@media (min-width: 1280px) {
  .xl\:opacity-0 { opacity: 0; }
  .xl\:opacity-25 { opacity: .25; }
  .xl\:opacity-50 { opacity: .5; }
  .xl\:opacity-75 { opacity: .75; }
  .xl\:opacity-100 { opacity: 1; }
}

@media (min-width: 1536px) {
  .\32 xl\:opacity-0 { opacity: 0; }
  .\32 xl\:opacity-25 { opacity: .25; }
  .\32 xl\:opacity-50 { opacity: .5; }
  .\32 xl\:opacity-75 { opacity: .75; }
  .\32 xl\:opacity-100 { opacity: 1; }
}

Print

Enabling the print option will also generate utility classes for print, which are only applied within the @media print { ... } media query.

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    print: true,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

Output:

CSS
.opacity-0 { opacity: 0; }
.opacity-25 { opacity: .25; }
.opacity-50 { opacity: .5; }
.opacity-75 { opacity: .75; }
.opacity-100 { opacity: 1; }

@media print {
  .print\:opacity-0 { opacity: 0; }
  .print\:opacity-25 { opacity: .25; }
  .print\:opacity-50 { opacity: .5; }
  .print\:opacity-75 { opacity: .75; }
  .print\:opacity-100 { opacity: 1; }
}

Dark

Enabling the dark option generates dark: prefixed classes scoped to @media (prefers-color-scheme: dark). This follows the same prefix convention as responsive and print utilities.

SCSS
$utilities: (
  "bg-color": (
    property: background-color,
    class: bg,
    dark: true,
    values: (
      white: #fff,
      black: #000,
    )
  )
);

Output:

CSS
.bg-white { background-color: #fff; }
.bg-black { background-color: #000; }

@media (prefers-color-scheme: dark) {
  .dark\:bg-white { background-color: #fff; }
  .dark\:bg-black { background-color: #000; }
}

Enabled

Set enabled: false to suppress output for a utility without removing it from the $utilities map. This is useful when you want to keep the utility's definition available for reference or downstream map.get() calls, but don't want it to emit any CSS.

SCSS
$utilities: map.merge(
  $utilities,
  (
    "float": map.merge(
      map.get($utilities, "float"),
      ( enabled: false ),
    ),
  )
);

Importance

Utilities generated by the API no longer include !important by default in v6. This is because we now use CSS layers to ensure utilities override components and modifier classes as intended. You can enable !important on a per-utility basis by setting the important option to true.

SCSS
$utilities: (
  "opacity": (
    property: opacity,
    important: true,
    values: (
      0: 0,
      25: .25,
      50: .5,
      75: .75,
      100: 1,
    )
  )
);

This will generate utilities with !important:

CSS
.opacity-0 { opacity: 0 !important; }
.opacity-25 { opacity: .25 !important; }
.opacity-50 { opacity: .5 !important; }
.opacity-75 { opacity: .75 !important; }
.opacity-100 { opacity: 1 !important; }

Using the API

Now that you’re familiar with how the utilities API works, learn how to add your own custom classes and modify our default utilities.

The examples below use two different import styles depending on the use case:

  • @use "@chassis-ui/css" with (...)—Use this when compiling the full Chassis CSS framework from a single entrypoint. Configuration is passed via with (...) and Chassis CSS handles merging in one step.
  • @use "@chassis-ui/css/scss/utilities" as * + @use "@chassis-ui/css/scss/utilities/api"—Use this for focused, utility-only builds where you need to read from or merge the existing $utilities map before passing it to the API layer. Using as * avoids a namespace prefix so you can reference $utilities directly.

Override utilities

Override existing utilities by using the same key and passing the new definition via the $utilities configuration variable when loading Chassis CSS. For example, take our existing overflow utility class definition:

"overflow": (
  property: overflow,
  values: auto hidden visible scroll,
),
"overflow-x": (
  property: overflow-x,
  values: auto hidden visible scroll,
),
"overflow-y": (
  property: overflow-y,
  values: auto hidden visible scroll,
),

If you want to make those responsive, and adjust the values used, you can do this:

SCSS
// my-chassis.scss
@use "@chassis-ui/css" with (
  $utilities: (
    "overflow": (
      responsive: true,
      // property: overflow,
      values: visible hidden scroll auto,
    ),
  )
);

Add utilities

New utilities can be added to the default $utilities map by passing them via the $utilities configuration variable when loading Chassis CSS. Chassis CSS merges your map on top of its defaults, so only supply the new utilities you want to add.

SCSS
// my-chassis.scss
@use "@chassis-ui/css" with (
  $utilities: (
    "cursor": (
      property: cursor,
      class: cursor,
      responsive: true,
      values: auto pointer grab,
    )
  )
);

Modify utilities

Modify existing utilities by passing a modified copy of the utility via the $utilities configuration variable. Use map.get and map.merge to build the new value from the existing definition. In the example below, we’re adding an additional value to the width utilities:

SCSS
// my-chassis.scss
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.merge(
  $utilities,
  (
    "width": map.merge(
      map.get($utilities, "width"),
      (
        values: map.merge(
          map.get(map.get($utilities, "width"), "values"),
          (10: 10%),
        ),
      ),
    ),
  )
);

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);

Enable responsive

You can enable responsive classes for an existing set of utilities that are not currently responsive by default. For example, to make the border classes responsive:

SCSS
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.merge(
  $utilities,
  (
    "border": map.merge(
      map.get($utilities, "border"),
      ( responsive: true ),
    ),
  )
);

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);

This will now generate responsive variations of .border and .border-0 for each breakpoint. Your generated CSS will look like this:

CSS
.border { ... }
.border-0 { ... }

@media (min-width: 576px) {
  .sm\:border { ... }
  .sm\:border-0 { ... }
}

@media (min-width: 768px) {
  .md\:border { ... }
  .md\:border-0 { ... }
}

@media (min-width: 1024px) {
  .lg\:border { ... }
  .lg\:border-0 { ... }
}

@media (min-width: 1280px) {
  .xl\:border { ... }
  .xl\:border-0 { ... }
}

@media (min-width: 1536px) {
  .2xl\:border { ... }
  .2xl\:border-0 { ... }
}

Rename utilities

Missing v4 utilities, or used to another naming convention? The utilities API can be used to override the resulting class of a given utility—for example, to rename .ms-* utilities to oldish .ml-*:

SCSS
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.merge(
  $utilities,
  (
    "margin-start": map.merge(
      map.get($utilities, "margin-start"),
      ( class: ml ),
    ),
  )
);

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);

Remove utilities

Remove any of the default utilities with the map.remove() Sass function.

SCSS
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.remove($utilities, "width", "float");

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);

You can also use the map.merge() Sass function and set the group key to null to remove the utility.

SCSS
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.merge(
  $utilities,
  (
    "width": null
  )
);

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);

Add, remove, modify

You can add, remove, and modify many utilities all at once with the map.merge() Sass function. Here’s how you can combine the previous examples into one larger map.

SCSS
@use "sass:map";
@use "@chassis-ui/css/scss/utilities" as *;

$utilities: map.merge(
  $utilities,
  (
    // Remove the `width` utility
    "width": null,
    // Make an existing utility responsive
    "border": map.merge(
      map.get($utilities, "border"),
      ( responsive: true ),
    ),
    // Add new utilities
    "cursor": (
      property: cursor,
      class: cursor,
      responsive: true,
      values: auto pointer grab,
    )
  )
);

@use "@chassis-ui/css/scss/utilities/api" with ($utilities: $utilities);