Bootstrap Theming Strategy: Sass Variables for Build-Time Structure, CSS Custom Properties for Runtime Color
Learn the supported Bootstrap theming decision: override Sass variables at compile time for structure and CSS custom properties at runtime for colors, keeping upgrades maintainable.
18 Feb 2026, 17:23 UTC

The problem with editing compiled CSS
When a Bootstrap project needs a custom look, the fastest path—editing the compiled CSS file—creates a maintenance trap. Every framework update overwrites your changes or forces a manual diff. The supported engineering decision is to separate concerns: use Sass variable overrides at compile time for structural values, and CSS custom properties at runtime for colors and component states. This keeps upgrades predictable and enables features like dark mode without recompiling.
How Bootstrap splits theming into two layers
Bootstrap 5 exposes theming through two distinct layers. The first is compile-time Sass variables and maps. These define values that generate utilities and component rules: $primary, $border-radius, $grid-breakpoints, and $theme-colors. The second is runtime CSS custom properties, such as --bs-primary and --bs-body-bg, which components reference directly in their stylesheets.
The practical rule is to override Sass variables for anything structural—spacing, radii, breakpoints—and CSS custom properties for colors and visual states that might change without a rebuild.
A minimal custom build
A minimal custom build requires importing Bootstrap's functions, variables, and maps before applying your overrides, then importing the framework itself. The order matters because Sass variables are evaluated at import time; setting $primary after @import 'bootstrap' has no effect on the already-emitted CSS.
// custom.scss
// 1. Load Bootstrap functions, variables, and maps first
@import 'bootstrap/functions';
@import 'bootstrap/variables';
@import 'bootstrap/maps';
// 2. Apply overrides before the framework import
$primary: #4f46e5;
$border-radius: 0.75rem;
// Add a custom color to the theme-colors map
$custom-colors: (
'brand': #6f42c1
);
$theme-colors: map-merge($theme-colors, $custom-colors);
// 3. Import the rest of Bootstrap
@import 'bootstrap';
Run this through your Sass compiler (for example, sass custom.scss custom.css from your project root). The generated CSS should contain your $primary value in button and utility rules, your $border-radius in component styles, and new .bg-brand and .text-brand utilities.
Runtime theming with CSS custom properties
For colors that need to change without recompiling—such as a dark mode toggle—redefine CSS custom properties under :root and [data-bs-theme="dark"]. Bootstrap 5.3 and later wire many component colors to these variables.
:root {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
}
[data-bs-theme='dark'] {
--bs-body-bg: #212529;
--bs-body-color: #f8f9fa;
}
This approach only works for properties actually bound to those variables. Not every component style is exposed as a custom property; verify in browser dev tools before assuming a property is themable.
Extending utility generation through Sass maps
Sass maps such as $theme-colors and $grid-breakpoints drive generated utilities. Adding a key to $theme-colors automatically produces text-*, bg-*, and btn-* utilities, whereas hand-editing CSS duplicates rules and invites drift. Prefer the Sass utility API or variable overrides to deep selectors and !important. High-specificity overrides—rules that are harder to override later—break the normal CSS cascade, complicate stacking with Bootstrap utilities, and make version upgrades harder to reason about.
Limits and common mistakes
- Import order: Importing the framework before your overrides makes Sass changes ineffective.
- Post-import changes: Changing Sass variables after
@import 'bootstrap'has no effect on emitted CSS. - CDN mixing: Combining local Sass overrides with a CDN build creates specificity conflicts because the CDN stylesheet is already compiled and may load after your custom rules.
- Variable coverage: CSS custom property overrides only apply where the component references the variable. If a component hardcodes a color, the variable override will not affect it.
- Version constraint: Built-in dark mode via
[data-bs-theme]requires Bootstrap 5.3 or later. Earlier versions do not include the color-mode variables or the toggle script.
How to verify the result
- Compile the Sass entry point and inspect the output CSS for the expected primary color and border-radius values.
- In a browser, set
data-bs-theme='dark'on thehtmlelement and confirm body background and component colors switch without a reload. - Add a custom key to
$theme-colors, rebuild, and check that utilities such as.bg-customand.text-customappear in the generated CSS. - If using the source distribution, run the project's Sass build or lint step to catch deprecation warnings from custom overrides.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.