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:
| Option | Type | Default value | Description |
|---|---|---|---|
property | Required | – | Name 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). |
values | Required | – | List 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. |
selector | Optional | class | Type of CSS selector in the generated CSS ruleset. Can be class, attr-starts, or attr-includes. |
child-selector | Optional | null | A child/descendant selector appended to the utility’s selector, wrapped in :where() for zero specificity. Use to target children instead of the element itself. |
class | Optional | null | Name 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. |
variables | Optional | null | List 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. |
group | Optional | false | Boolean. 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. |
state | Optional | null | List of pseudo-class variants (e.g., :hover or :focus) to generate as prefix-style classes. |
responsive | Optional | false | Boolean indicating if responsive classes should be generated. |
important | Optional | false | Boolean indicating if !important should be added to the utility’s CSS rules. |
print | Optional | false | Boolean indicating if print classes need to be generated. |
dark | Optional | false | Boolean indicating if dark: prefixed classes should be generated, scoped to @media (prefers-color-scheme: dark). |
enabled | Optional | true | Set 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:
$utilities: (
"opacity": (
property: opacity,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);Which outputs the following:
.opacity-0 { opacity: 0; }
.opacity-25 { opacity: .25; }
.opacity-50 { opacity: .5; }
.opacity-75 { opacity: .75; }
.opacity-100 { opacity: 1; }property
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:
$utilities: (
"text-decoration": (
property: text-decoration,
values: none underline line-through
)
);Output:
.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:
$utilities: (
"bg-color": (
property: (
"--bg-color": null,
"background-color": var(--bg-color)
),
class: bg,
values: (
primary: var(--primary),
danger: var(--danger),
)
)
);Output:
.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:
$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:
.text-small { font-size: .875rem; line-height: 1.25; }
.text-large { font-size: 1.25rem; line-height: 1.75; }values
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:
values: none underline line-throughAs a map, like with opacity utilities:
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:
values: $position-valuesselector
| Option | Description |
|---|---|
| No selector | Defaults 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-includes—the 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-"]:
$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:
[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:
$utilities: (
"space-x": (
property: margin-inline-end,
class: space-x,
child-selector: "> :not(:last-child)",
values: (
1: .25rem,
2: .5rem,
3: 1rem,
)
)
);Output:
: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():
$utilities: (
"striped-bg": (
property: background-color,
class: striped,
child-selector: "> :nth-child(odd)",
values: (
null: var(--cx-bg-even),
)
)
);Output:
:where(.striped > :nth-child(odd)) { background-color: var(--cx-bg-even); }Or target all direct children with > *:
$utilities: (
"child-rounded": (
property: border-radius,
class: child-rounded,
child-selector: "> *",
values: (
null: var(--cx-border-radius),
0: 0,
)
)
);Output:
: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-*:
$utilities: (
"opacity": (
property: opacity,
class: o,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);Output:
.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:
$utilities: (
"visibility": (
property: visibility,
class: null,
values: (
visible: visible,
invisible: hidden,
)
)
);Output:
.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:
$utilities: (
"link-opacity": (
property: color,
class: link,
variables: link-color,
values: (
10: 10%,
50: 50%,
100: 100%,
)
),
);Output:
.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:
$utilities: (
"link-underline": (
property: text-decoration-color,
class: link-underline,
variables: (
"link-underline-opacity": 1
),
values: (...)
)
);Output:
.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:
- One shared rule targeting every generated selector — containing the static (non-null) property values.
- 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):
$utilities: (
"text-context": (
property: (
"--fg-color": null,
"color": var(--fg-color),
),
class: text,
group: true,
values: (
primary: var(--primary),
danger: var(--danger),
)
)
);Output:
/* 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:
$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:
.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:
$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:
.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.
$utilities: (
"opacity": (
property: opacity,
class: opacity,
state: hover,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);Output:
.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.
$utilities: (
"opacity": (
property: opacity,
responsive: true,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);Output:
.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; }
}Enabling the print option will also generate utility classes for print, which are only applied within the @media print { ... } media query.
$utilities: (
"opacity": (
property: opacity,
print: true,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);Output:
.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.
$utilities: (
"bg-color": (
property: background-color,
class: bg,
dark: true,
values: (
white: #fff,
black: #000,
)
)
);Output:
.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.
$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.
$utilities: (
"opacity": (
property: opacity,
important: true,
values: (
0: 0,
25: .25,
50: .5,
75: .75,
100: 1,
)
)
);This will generate utilities with !important:
.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 viawith (...)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$utilitiesmap before passing it to the API layer. Usingas *avoids a namespace prefix so you can reference$utilitiesdirectly.
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:
// 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.
// 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:
// 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:
@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:
.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-*:
@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.
@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.
@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.
@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);