CSS Variables and Theming

Intermediate
12 min

CSS Variables and Theming

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.

Root Variables

Open the compiled bootstrap.css and the first rule defines the theme on :root:

css
: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.

What Root Variables Do Not Change

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).
  • Links, focus rings and anything reading the variable directly.

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.

Component Variables

Every component defines its own variables on its base class, prefixed with the component name. From .btn:

css
.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:

css
.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.

Inline Overrides

Because variables are inherited, one element can be themed inline without a class:

html
<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.

Runtime Theme Switching

Set variables from JavaScript to react to user choices:

javascript
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.

Sass vs. CSS Variables

| 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.

Common Mistakes

  • Changing --bs-primary and expecting .btn-primary to update. Override --bs-btn-* on the button or rebuild with Sass.
  • Forgetting the -rgb twin. Utilities using rgba(var(--bs-primary-rgb), ...) keep the old color.
  • Overriding on the wrong element. Component variables must be set on the component's base element or an ancestor, not on a child.
  • Guessing variable names. Check the compiled CSS; names follow --bs-{component}-{property} but not every property is exposed.
Quick Quiz
Question 1 of 3

Why does changing `--bs-primary` alone not recolor `.btn-primary`?

Key Takeaways

  • Root variables (--bs-primary, --bs-body-bg, --bs-border-radius) describe the theme; component variables (--bs-btn-bg, --bs-card-border-color) describe one component.
  • Utilities read root variables and their -rgb twins, so change both together.
  • Component variants are just variable assignments; a custom .btn-brand needs only a few --bs-btn-* declarations.
  • Variables can be set per element inline or from JavaScript at runtime, which Sass cannot do.
  • Use Sass for global structural changes and CSS variables for instance-level and runtime theming.

Next lesson: Customizing Bootstrap with Sass and Building a Page - override variables, maps and imports at build time and assemble a complete page.

CSS Variables and Theming - Bootstrap | CodeYourCraft | CodeYourCraft