Utilities and Sass customisation

The utility API in practice, colour modes driven by data-bs-theme, and rebuilding Bootstrap from Sass to change the design system properly.

Utilities and helper classes

<div class="d-flex align-items-center gap-3 p-3 mb-4 rounded-3 border bg-body-tertiary shadow-sm">
  <span class="text-truncate flex-fill fs-5 fw-semibold">A long title that truncates</span>
  <button class="btn btn-sm btn-outline-secondary" aria-label="Close">&times;</button>
</div>

<p class="text-body-secondary small mb-0">Fine print</p>
<span class="visually-hidden">Screen-reader label</span>
FamilyExamplesNotes
Displayd-none, d-lg-flexresponsive display, mobile first
Flexjustify-content-between, align-items-centerthe whole flexbox API
Spacingm-3, px-4, gap-20 to 5 scale plus auto
Texttext-center, text-truncate, text-body-secondaryalignment and theme-aware colour
Backgroundbg-primary-subtle, bg-bodycolours that follow the theme
Bordersborder, border-0, rounded-pilledges and radius
Sizingw-100, vh-100, mw-100width and height
Positionposition-relative, top-0, translate-middlepositioning and transforms
Accessibilityvisually-hidden, invisiblehidden from sight, not from a screen reader

Colour modes

<html lang="en" data-bs-theme="dark">
const root = document.documentElement;

// follow the operating system on first visit
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
root.setAttribute('data-bs-theme', localStorage.getItem('theme') || (prefersDark ? 'dark' : 'light'));

toggle.addEventListener('click', () => {
  const next = root.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
  root.setAttribute('data-bs-theme', next);
  localStorage.setItem('theme', next);
});

Setting data-bs-theme on any element switches that subtree. Components are built from CSS custom properties such as --bs-body-bg and --bs-border-color, so they follow the attribute without extra classes or a second stylesheet.

Customising with Sass

// custom.scss - import the source files, never the compiled CSS
$primary: #6d28d9;
$border-radius: 0.75rem;
$font-family-sans-serif: "Inter", system-ui, sans-serif;
$enable-negative-margins: true;

@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";

// only the parts you actually use
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/utilities/api";
  • Overrides must be declared before the Bootstrap variables are imported, or the defaults are already set and your value is ignored.
  • With the modern Sass module system the whole thing collapses to @use "bootstrap/scss/bootstrap" with ($primary: #6d28d9);.
  • Extending a map needs map-merge: $theme-colors: map-merge($theme-colors, (brand: $primary));.
  • Importing only the components you use is the reliable way to shrink the compiled file; editing the shipped CSS is not.
💡
Rebuilding Bootstrap from Sass is the supported way to change the design system. Editing the compiled bootstrap.min.css keeps both the file size and the maintenance cost, and every change is lost on the next upgrade.

FAQ

Can I customise Bootstrap without Sass?
Partly. Anything driven by CSS custom properties (colour modes, body background, most component colours) can be overridden with plain CSS loaded after the stylesheet. Values baked into class generation, such as the grid breakpoints and which utilities exist, need Sass.
Why did my Sass override have no effect?
The import order is wrong: the default variable was declared before your value. Put every override above the first Bootstrap import.

Navbar, modal and forms CSS: getting started

Last refreshed 2026-09-18.