Utilities
Small set of single-property classes for tweaking existing components.
Philosophy
A utility class changes one property on an existing shape. A button that needs a custom height. A card row that needs to be flex. A stack that needs more gap. A heading that needs muted color in one specific place. That’s what utilities are for.
What utilities are not for: assembling a new component from scratch. If you find yourself stacking .d-flex .gap-3 .p-4 .rounded .border on a <div> to build something that looks like a card, you’re using the wrong tool. Build the component in SCSS with its own BEM class, tokens, and color-mix states. The component file is small, scoped, deletable, and lets the next person reading your codebase see what the thing actually is.
Stisla is a design spec realized as components. Utilities are escape hatches. They aren’t Lego bricks. If composing utilities into components is your preferred workflow, reach for Tailwind instead.
The shipped set is small on purpose. Text (color, weight, size, line-height, alignment, transform, overflow). Layout (display, flex, gap, margin, padding, sizing, position, overflow). If a property isn’t in those two lists, the answer isn’t “add a new utility”. It’s “the component owns this”.
Text
Typography tweaks. Class names track the underlying property. Use .fw-* for weight, .fs-* for size, .lh-* for line height, and .text-* for color, alignment, transform, and overflow.
Semibold, fs-5, primary color.
Light weight, muted color.
A long sentence that will truncate with an ellipsis once it exceeds the container width.
Uppercased, centered, fs-1.
<div class="d-flex flex-column">
<p class="fw-semibold fs-5 text-primary">Semibold, fs-5, primary color.</p>
<p class="fw-light text-muted-foreground">Light weight, muted color.</p>
<p class="text-truncate" style="max-width: 18rem;">A long sentence that will truncate with an ellipsis once it exceeds the container width.</p>
<p class="text-uppercase text-center fs-1">Uppercased, centered, fs-1.</p>
</div>Color
| Class | Reads | Use |
|---|---|---|
.text-foreground | --st-foreground | Reset child color to body text. |
.text-muted-foreground | --st-muted-foreground | Secondary copy, helper text, metadata. |
.text-primary | --st-primary | Brand-colored text run. |
.text-success | --st-success | Positive state text. |
.text-warning | --st-warning | Caution state text. |
.text-danger | --st-danger | Error state text. |
.text-info | --st-info | Informational text. |
Weight
| Class | Value |
|---|---|
.fw-light | 300 |
.fw-normal | 400 |
.fw-medium | 500 |
.fw-semibold | 600 |
.fw-bold | 700 |
Size
Numeric scale, ascending small → large. Same direction as .gap-* and .m*-*. Use .fs-{breakpoint}-{step} for breakpoint-up variants.
| Class | Value | Pixels (at 16 px root) |
|---|---|---|
.fs-1 | 0.75rem | 12 |
.fs-2 | 0.875rem | 14 |
.fs-3 | 1rem | 16 |
.fs-4 | 1.125rem | 18 |
.fs-5 | 1.25rem | 20 |
.fs-6 | 1.5rem | 24 |
.fs-7 | 2rem | 32 |
Line height
| Class | Value |
|---|---|
.lh-1 | 1 |
.lh-sm | 1.25 |
.lh-base | 1.5 |
.lh-lg | 2 |
Alignment, transform, overflow
| Class | Effect |
|---|---|
.text-start | Align start of text container (LTR=left). |
.text-end | Align end (LTR=right). |
.text-center | Center align. |
.text-lowercase | Force lowercase. |
.text-uppercase | Force uppercase. |
.text-capitalize | Capitalize first letter of each word. |
.text-nowrap | Prevent line breaking. |
.text-truncate | Single-line ellipsis on overflow. |
.text-break | Break long words to prevent overflow. |
Accessibility
| Class | Effect |
|---|---|
.visually-hidden | Removes the element from view while keeping it readable by assistive tech. Used for spinner labels, icon-only button labels, skip links. |
Layout
Spacing uses a Tailwind-style 4 px linear scale (1=4 px, 2=8 px, 3=12 px, 4=16 px, 5=20 px, 6=24 px, 8=32 px). Every value multiplies by --st-density so the global density knob retunes spacing alongside component padding.
Margin and padding use CSS logical properties (margin-block-start, margin-inline-end, ...) so the same class works in LTR and RTL.
<div class="d-flex gap-3 align-items-center">
<button type="button" class="btn btn--primary">Save</button>
<button type="button" class="btn btn--neutral">Cancel</button>
<span class="text-muted-foreground fs-2 ms-auto">Edited 2 min ago</span>
</div>One flex row with a 12 px gap and an end-aligned timestamp pushed by .ms-auto. No new component, just three buttons and a span getting a layout tweak.
We'll never share your address.
<div class="d-flex flex-column gap-2" style="max-width: 18rem;">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" class="input" placeholder="you@example.com" />
<p class="text-muted-foreground fs-2 mb-0">We'll never share your address.</p>
</div>A vertical stack with an 8 px gap between rows. Note the helper text uses .mb-0 to kill its natural bottom margin so the gap owns the rhythm.
Display
| Class | Value |
|---|---|
.d-none | display: none |
.d-block | display: block |
.d-inline | display: inline |
.d-inline-block | display: inline-block |
.d-flex | display: flex |
.d-inline-flex | display: inline-flex |
.d-grid | display: grid |
.d-inline-grid | display: inline-grid |
Flex direction & wrap
| Class | Value |
|---|---|
.flex-row | flex-direction: row |
.flex-row-reverse | row-reverse |
.flex-column | column |
.flex-column-reverse | column-reverse |
.flex-wrap | flex-wrap: wrap |
.flex-nowrap | nowrap |
.flex-wrap-reverse | wrap-reverse |
Flex grow & shrink
| Class | Value |
|---|---|
.flex-fill | flex: 1 1 auto |
.flex-grow-0 | flex-grow: 0 |
.flex-grow-1 | flex-grow: 1 |
.flex-shrink-0 | flex-shrink: 0 |
.flex-shrink-1 | flex-shrink: 1 |
Justify & align
| Class | Value |
|---|---|
.justify-content-start | flex-start |
.justify-content-end | flex-end |
.justify-content-center | center |
.justify-content-between | space-between |
.justify-content-around | space-around |
.justify-content-evenly | space-evenly |
.align-items-{start,end,center,baseline,stretch} | Cross-axis alignment for flex children. |
.align-self-{start,end,center,baseline,stretch,auto} | Per-child override of cross-axis alignment. |
Gap
.gap-{n} sets both row and column gap. .row-gap-{n} and .column-gap-{n} target one axis. Values: 0 / 1 / 2 / 3 / 4 / 5 / 6 / 8.
| Step | Value | Default density (1) |
|---|---|---|
0 | 0 | 0 px |
1 | 0.25rem × density | 4 px |
2 | 0.5rem × density | 8 px |
3 | 0.75rem × density | 12 px |
4 | 1rem × density | 16 px |
5 | 1.25rem × density | 20 px |
6 | 1.5rem × density | 24 px |
8 | 2rem × density | 32 px |
Margin & padding
Direction abbreviations follow Bootstrap convention: top, bottom, start, end, x for both inline sides, y for both block sides. Start and end use logical properties so RTL flips for free. Same step scale as gap.
| Pattern | Maps to |
|---|---|
.m-{0…8} | margin (all sides) |
.mt-{0…8} | margin-block-start |
.mb-{0…8} | margin-block-end |
.ms-{0…8} | margin-inline-start (LTR=left) |
.me-{0…8} | margin-inline-end (LTR=right) |
.mx-{0…8} | margin-inline (both) |
.my-{0…8} | margin-block (both) |
.m-auto, .mx-auto, .ms-auto, .me-auto, .mt-auto, .mb-auto, .my-auto | Auto margin variants for centering and flex push-to-end. |
.p-{0…8}, .pt-, .pb-, .ps-, .pe-, .px-, .py- | Same shape, padding properties. |
Sizing
Percentage widths and heights resolve against the containing block. .h-* needs a sized parent to be meaningful.
| Class | Value |
|---|---|
.w-25 | width: 25% |
.w-50 | width: 50% |
.w-75 | width: 75% |
.w-100 | width: 100% |
.w-auto | width: auto |
.h-{25,50,75,100,auto} | Same shape, height. |
.mw-100 | max-width: 100% |
.mh-100 | max-height: 100% |
Position
| Class | Value |
|---|---|
.position-static | static |
.position-relative | relative |
.position-absolute | absolute |
.position-fixed | fixed |
.position-sticky | sticky |
Overflow
| Class | Value |
|---|---|
.overflow-{hidden,auto,scroll,visible} | Both axes. |
.overflow-x-{hidden,auto,scroll,visible} | Horizontal axis. |
.overflow-y-{hidden,auto,scroll,visible} | Vertical axis. |
Responsive
Insert a breakpoint name into any responsive-enabled class to make it apply from that width up. The mobile-first base lays the default. Each breakpoint cascades on top via min-width.
md up.<div class="d-flex flex-column flex-md-row gap-3 align-items-md-center">
<div class="w-100 w-md-50">
<div class="card">
<div class="card__body">Stacks on mobile.</div>
</div>
</div>
<div class="w-100 w-md-50">
<div class="card">
<div class="card__body">Sits side‑by‑side from <code>md</code> up.</div>
</div>
</div>
</div>Resize the preview frame. Below 768 px the row stacks (.flex-column) with each card at .w-100; at 768 px and above .flex-md-row + .w-md-50 kick in and the cards split the row.
Breakpoints
Same five breakpoints as the grid system.
| Suffix | Applies at | Width ≥ |
|---|---|---|
| (none) | Always (mobile default) | 0 |
sm | Small tablet up | 576 px |
md | Tablet up | 768 px |
lg | Small laptop up | 992 px |
xl | Desktop up | 1200 px |
xxl | Wide desktop up | 1400 px |
What gets a responsive variant
These are the defaults. Every group is toggleable through $utilities-config, so this table shows what ships when the bundle is imported untouched.
| Group | Pattern | Example |
|---|---|---|
| Display | .d-{bp}-{value} | .d-md-flex, .d-lg-none |
| Flex direction & wrap | .flex-{bp}-{value} | .flex-md-row, .flex-lg-wrap |
| Justify content | .justify-content-{bp}-{value} | .justify-content-md-between |
| Align items | .align-items-{bp}-{value} | .align-items-md-center |
| Gap | .gap-{bp}-{step}, .row-gap-{bp}-{step}, .column-gap-{bp}-{step} | .gap-md-4 |
| Margin | .m{,t,b,s,e,x,y}-{bp}-{step} | .mt-md-3, .mx-lg-auto |
| Padding | .p{,t,b,s,e,x,y}-{bp}-{step} | .p-md-4, .py-lg-2 |
| Width | .w-{bp}-{25,50,75,100,auto} | .w-md-50 |
| Text align | .text-{bp}-{start,end,center} | .text-md-center |
| Font size | .fs-{bp}-{step} | .fs-md-4 |
Base-only by default: flex grow / shrink, align self, height, max-width / max-height, position, overflow, text color, font weight, line height, text transform, visually-hidden. Flip any of them on in $utilities-config.
Customizing the set
$utilities-config is the single override surface. Every group has an enabled flag and a responsive flag. Override the map before importing the bundle.
Toggling a group
Set responsive: true to add breakpoint variants to a group that ships base-only, or enabled: false to drop a group entirely.
// Override before the bundle import. map-merge preserves all other defaults.
$utilities-config: map-merge($utilities-config, (
overflow: (enabled: true, responsive: true),
position: (enabled: true, responsive: true),
visually-hidden: (enabled: false, responsive: false),
));
@import '@stisla/css/bundles/stisla';Emits .overflow-md-hidden, .position-lg-sticky, etc., and drops every .visually-hidden* class from the output. Disabled groups cost zero bytes.
Adding a new utility
You need two pieces. An entry in $utilities-config, and a matching @include utility-group(…) block. The helper handles base and breakpoint emission. $i is the empty string at base, and -sm / -md / … inside each breakpoint media query.
@import '@stisla/css/utilities/utilities';
$utilities-config: map-merge($utilities-config, (
cursor: (enabled: true, responsive: false),
));
@include utility-group('cursor') using ($i) {
.cursor#{$i}-pointer { cursor: pointer !important; }
.cursor#{$i}-grab { cursor: grab !important; }
.cursor#{$i}-not-allowed { cursor: not-allowed !important; }
}Emits .cursor-pointer, .cursor-grab, .cursor-not-allowed. Flip responsive: true and the same block also emits .cursor-md-pointer etc. at every breakpoint.
All groups
| Group key | Classes | Responsive by default |
|---|---|---|
text-color | .text-foreground, .text-primary, … | no |
font-weight | .fw-* | no |
font-size | .fs-* | yes |
line-height | .lh-* | no |
text-transform | .text-lowercase, .text-uppercase, .text-capitalize | no |
text-wrap | .text-nowrap, .text-truncate, .text-break | no |
text-align | .text-start, .text-end, .text-center | yes |
visually-hidden | .visually-hidden | no |
display | .d-* | yes |
position | .position-* | no |
overflow | .overflow-*, .overflow-x-*, .overflow-y-* | no |
flex-fill | .flex-fill, .flex-grow-*, .flex-shrink-* | no |
flex-direction | .flex-row, .flex-column, … | yes |
flex-wrap | .flex-wrap, .flex-nowrap, .flex-wrap-reverse | yes |
justify-content | .justify-content-* | yes |
align-items | .align-items-* | yes |
align-self | .align-self-* | no |
gap | .gap-*, .row-gap-*, .column-gap-* | yes |
margin | .m{,t,b,s,e,x,y}-* | yes |
padding | .p{,t,b,s,e,x,y}-* | yes |
width | .w-* | yes |
height | .h-* | no |
max-size | .mw-100, .mh-100 | no |
Customization
Two knobs, no per-class vars.
Spacing values resolve through --st-density, the global lever from Customization that retunes every .gap-*, .m*-*, and .p*-* alongside component padding. Compact density (0.875) shrinks every utility step proportionally. Comfortable density (1.125) expands them.
Which groups ship, and which carry breakpoint variants, is set in $utilities-config. Disable groups you don’t use to drop them from the bundle. Flip responsive: true on a group to add breakpoint variants.