Bootstrap 5 exposes hundreds of CSS custom properties. Root-level variables such as --bs-primary and --bs-body-bg describe the theme; component-level variables such as --bs-btn-bg and --bs-card-border-color describe one component and are set on its base class. Because they are plain CSS, you can change them without a Sass build, at runtime, and per element. This lesson explains the two layers, what you can and cannot change with them, and how to create component variants and live theme switching.
Open the compiled bootstrap.css and the first rule defines the theme on :root:
:root, [data-bs-theme="light"] {
--bs-blue: #0d6efd;
--bs-primary: #0d6efd;
--bs-primary-rgb: 13, 110, 253;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, ...;
--bs-body-font-size: 1rem;
--bs-body-color: #212529;
--bs-body-bg: #fff;
--bs-border-color: #dee2e6;
--bs-border-radius: 0.375rem;
--bs-link-color: #0d6efd;
/* ... */
}Groups you will use most:
| Group | Examples |
|---|---|
| Theme colors | --bs-primary, --bs-danger, plus -rgb, -text-emphasis, -bg-subtle, -border-subtle variants |
| Body | --bs-body-bg, --bs-body-color, --bs-body-font-family, --bs-body-line-height |
| Surfaces | --bs-secondary-bg, --bs-tertiary-bg, --bs-emphasis-color |
| Borders | --bs-border-color, --bs-border-width, --bs-border-radius, --bs-border-radius-lg |
| Links | --bs-link-color, --bs-link-hover-color, --bs-link-decoration |
| Focus | --bs-focus-ring-color, --bs-focus-ring-width |
The -rgb variants exist because utilities such as bg-primary are written as rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) so that bg-opacity-* can adjust transparency. If you change --bs-primary, change --bs-primary-rgb too.
Bootstrap's Sass still bakes many values into the compiled CSS. Changing --bs-primary at runtime updates:
bg-primary, text-primary, border-primary utilities (through the -rgb variable).It does not update .btn-primary, .alert-primary, .badge.text-bg-primary or the :hover shades of components, because those are compiled from the Sass map into component variables. For those, either override the component variables (next section) or use the Sass build. This is the single most common surprise when theming with variables alone.
Every component defines its own variables on its base class, prefixed with the component name. From .btn:
.btn {
--bs-btn-padding-x: 0.75rem;
--bs-btn-padding-y: 0.375rem;
--bs-btn-font-size: 1rem;
--bs-btn-color: var(--bs-body-color);
--bs-btn-bg: transparent;
--bs-btn-border-radius: var(--bs-border-radius);
--bs-btn-hover-bg: transparent;
--bs-btn-active-bg: transparent;
--bs-btn-disabled-opacity: 0.65;
--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);
}Variants such as .btn-primary only reassign these variables. That means a new button style is a handful of variable declarations, as in the .btn-brand example at the top of the lesson, and it automatically gets hover, focus, active and disabled behavior.
The same applies across the library:
.card.highlight {
--bs-card-border-color: var(--bs-warning);
--bs-card-cap-bg: var(--bs-warning-bg-subtle);
--bs-card-border-radius: 1rem;
}
.table.compact {
--bs-table-bg: transparent;
--bs-table-striped-bg: var(--bs-tertiary-bg);
--bs-table-hover-bg: var(--bs-secondary-bg);
}
.progress.thin {
--bs-progress-height: 4px;
--bs-progress-bar-bg: var(--bs-success);
}Look up a component's variables in its compiled CSS or the "CSS" section of its documentation page.
Because variables are inherited, one element can be themed inline without a class:
<div class="alert alert-primary" style="--bs-alert-bg: #eef2ff; --bs-alert-border-color: #c7d2fe; --bs-alert-color: #3730a3">
Custom tinted alert
</div>
<nav class="navbar" style="--bs-navbar-padding-y: 1rem; --bs-navbar-brand-font-size: 1.5rem">...</nav>This is ideal for user-configurable colors loaded from a database, where a Sass build is impossible.
Set variables from JavaScript to react to user choices:
const root = document.documentElement;
function applyBrand(hex) {
const [r, g, b] = hex.match(/\w\w/g).map((h) => parseInt(h, 16));
root.style.setProperty("--bs-primary", hex);
root.style.setProperty("--bs-primary-rgb", `${r}, ${g}, ${b}`);
root.style.setProperty("--bs-link-color", hex);
}
applyBrand("#0f766e");Combine this with a .btn-brand class that reads var(--bs-primary) and the whole interface follows the chosen color instantly.
| Need | Use | |---|---| | Change component colors, shades, hover states globally | Sass variables and maps | | Remove unused components, change breakpoints, grid columns | Sass | | Tweak a component instance, user-selected colors, no build step | CSS variables | | Dark mode and custom color modes | CSS variables (generated by Sass) |
Most projects use both: Sass for the base theme, CSS variables for runtime and per-instance adjustments.
--bs-primary and expecting .btn-primary to update. Override --bs-btn-* on the button or rebuild with Sass.-rgb twin. Utilities using rgba(var(--bs-primary-rgb), ...) keep the old color.--bs-{component}-{property} but not every property is exposed.Why does changing `--bs-primary` alone not recolor `.btn-primary`?
--bs-primary, --bs-body-bg, --bs-border-radius) describe the theme; component variables (--bs-btn-bg, --bs-card-border-color) describe one component.-rgb twins, so change both together..btn-brand needs only a few --bs-btn-* declarations.Next lesson: Customizing Bootstrap with Sass and Building a Page - override variables, maps and imports at build time and assemble a complete page.