Bootstrap Sass Variables and the Override Order
If you have ever changed a Bootstrap Sass variable and watched nothing happen, you ran into the single most common Bootstrap theming mistake: setting a variable in the wrong place. Bootstrap 5’s variables only work if you override them before Bootstrap reads them. This guide explains how the variable system actually works, the exact order to write your overrides, and the structure we use after parsing all 1,000+ variables for the bootstrap.build editor.
The one rule that trips everyone up
Every Bootstrap Sass variable is declared with !default. That flag means “use this value unless it was already set.” So Bootstrap reads your override only if your assignment comes earlier in the compile than Bootstrap’s own declaration. Override after the import and your value is simply ignored, because the variable already has a value by the time your line runs.
That single fact dictates the whole structure of a custom Bootstrap build:
// 1. Bootstrap functions (needed so your overrides can use them)
@import "bootstrap/scss/functions";
// 2. YOUR overrides go here, before variables
$primary: #6c4cf1;
$body-bg: #fbfbfd;
$border-radius: 0.5rem;
// 3. Bootstrap variables, maps, and the rest
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/root";
// 4. Only the parts you need (or "bootstrap/scss/bootstrap" for everything)
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/buttons";
// ...
Functions come first because some variables are built from functions (for example tint-color() and shift-color()). Your overrides come next. Everything else follows. Get this order right and customization becomes predictable.
How !default really works
Here is the mental model. Bootstrap’s _variables.scss is full of lines like this:
$primary: $blue !default;
$body-color: $gray-900 !default;
$border-radius: 0.375rem !default;
When Sass compiles your file top to bottom, it hits your $primary: #6c4cf1; first. Later, Bootstrap’s $primary: $blue !default; runs, sees that $primary already has a value, and does nothing. Your value wins. Remove your line and Bootstrap’s default ($blue) applies instead.
This is why “override before import” is not a style preference. It is the mechanism.
The three tiers of Bootstrap variables
Bootstrap’s variables are not a flat list. They form a dependency chain, and knowing the tiers tells you which variable to change to get the effect you want without breaking ten other things.
| Tier | Examples | What it does |
|---|---|---|
| Primitives | $blue, $gray-500, $spacer, $font-family-sans-serif |
Raw values with no Bootstrap meaning yet |
| Semantic | $primary, $body-bg, $body-color, $border-radius |
Roles built from primitives |
| Component | $btn-padding-y, $card-border-radius, $input-bg, $navbar-padding-y |
Per-component values, usually built from semantic ones |
The chain flows downhill. $card-border-radius defaults to $border-radius, which is its own value. $btn-color resolves against $primary, which defaults to $blue. So changing one primitive can cascade into dozens of components. When we built the editor, mapping this graph was the hard part: a single change to $border-radius touches buttons, cards, inputs, dropdowns, alerts, and more, because they all inherit from it by default.
The practical takeaway: change the highest tier that gets you the result. Want rounder everything? Set $border-radius once. Want only rounder buttons? Set $btn-border-radius. Reaching for the lowest-level variable when a higher one exists is how themes become inconsistent.
Maps, not just variables
Some of Bootstrap’s most important settings are Sass maps, not single variables: $theme-colors, $spacers, $grid-breakpoints, $font-sizes, and others. Maps power the utility classes and the color system, so editing them is how you add a brand color or a custom spacing step.
You can replace a map wholesale, but the safer move is to merge so you keep Bootstrap’s defaults:
// Add a brand color without losing primary, secondary, etc.
$custom-colors: (
"brand": #ff5c8a,
);
$theme-colors: map-merge($theme-colors, $custom-colors);
That one merge generates .btn-brand, .bg-brand, .text-brand, .border-brand, and the rest of the color utilities, because Bootstrap loops over $theme-colors to build them. Maps must be merged after variables (where $theme-colors is defined) but before maps and the components that consume them. For the full color workflow, see our guide on customizing Bootstrap 5 colors.
Sass variables vs CSS variables
Bootstrap 5.3 ships two parallel systems, and confusing them is the second most common mistake.
- Sass variables (
$primary) are resolved at compile time. They decide what your CSS is. You cannot change them in the browser. - CSS variables (
--bs-primary) are emitted into your compiled CSS and resolved at runtime. You can override them live, per element or per theme, with no recompile.
/* Runtime tweak, no Sass build needed */
.card.promo {
--bs-primary-rgb: 255, 92, 138;
}
Use Sass variables to define your theme once. Use CSS variables for runtime variations like dark mode or a single highlighted section. The data-bs-theme attribute that drives Bootstrap 5 dark mode is built entirely on CSS variables, which is exactly why it can switch instantly without recompiling.
Common override mistakes we see
After helping people debug a lot of themes, the same handful of issues come up again and again:
- Overriding after the import. The value is ignored because of
!default. Move it above@import "bootstrap/scss/variables". - Forgetting the functions import. If your override uses
tint-color(),darken(), orrgba($primary, .5), you needfunctionsimported first or Sass throws an undefined-function error. - Editing a map before it exists.
map-merge($theme-colors, ...)fails if it runs beforevariablesdefines$theme-colors. - Changing a component variable when a semantic one would do. Setting
$btn-border-radius,$card-border-radius, and$input-border-radiusseparately when you really wanted$border-radiusonce. - Expecting
!importantto fix Sass.!importantis a CSS concept. A Sass override that runs in the wrong order will not be rescued by it.
Skip the boilerplate
You do not have to memorize the import order to get it right. The bootstrap.build editor loads every Bootstrap 5.3 variable in the correct sequence, shows you the dependency between primitives, semantic roles, and components as you edit, and recompiles a live preview in the browser. When you are done it exports a clean _custom.scss with your overrides already placed before the Bootstrap import, plus the compiled CSS if you just want the output. It is the same variable graph described above, made visual, so you change the highest-tier variable and immediately see everywhere it lands.
Next steps
This variable system is the engine behind the complete guide to customizing Bootstrap 5. With it clear, the natural follow-ups are the color and type guides, which apply these rules to specific themes: customize Bootstrap 5 colors, change fonts and typography, and add dark mode. When your theme is ready, export it straight from the builder and drop it into your project.
FAQ
Why is my Bootstrap variable override not working?
Almost always because it runs after @import "bootstrap/scss/variables". Bootstrap variables use !default, so they only accept your value if you set it earlier in the file. Move every override above the variables import (and below the functions import).
Do I need to import all of Bootstrap to change variables?
No. Import functions, set your overrides, then import only variables, maps, mixins, and the specific component partials you use. Importing only the partials you use is also the simplest way to keep Bootstrap’s compiled CSS small.
What is the difference between $primary and --bs-primary?
$primary is a Sass variable resolved at compile time; it decides what CSS is generated. --bs-primary is a CSS custom property in the output that you can override live in the browser, per element or per theme, without recompiling.
How do I add a custom theme color?
Merge it into $theme-colors after the variables import: $theme-colors: map-merge($theme-colors, ("brand": #ff5c8a));. Bootstrap then generates the matching .btn-brand, .bg-brand, and .text-brand utilities for you.
Try it in the builder
Make these changes visually with a live preview, then export clean Bootstrap Sass or CSS.
Open the Builder