Bootstrap 5.3 introduced color modes: a built-in dark theme that every component supports, switched by a single data-bs-theme attribute. Because the implementation is based on CSS variables, you can also scope dark styling to one section, honor the user's system preference, and build your own modes. After this lesson you will be able to ship a light/dark/auto toggle and make custom CSS that adapts automatically.
Set data-bs-theme="dark" on the <html> element and every component switches:
<!doctype html>
<html lang="en" data-bs-theme="dark">Nothing else changes. Buttons, cards, forms, modals, dropdowns and tables all read their colors from CSS variables that the attribute redefines. The attribute works on any element, so you can also make a single region dark:
<div class="p-4 rounded" data-bs-theme="dark">
<div class="card">
<div class="card-body">A dark card inside a light page.</div>
</div>
</div>
<nav class="navbar bg-body-tertiary" data-bs-theme="dark">...</nav>The navbar example replaces the old .navbar-dark class, which is deprecated in 5.3.
Hard-coded colors such as bg-white or text-dark stay white and dark in dark mode, which is usually wrong. Use the semantic utilities instead:
| Utility | Light | Dark |
|---|---|---|
| bg-body | white | near black |
| bg-body-secondary | light grey | dark grey |
| bg-body-tertiary | lighter grey | darker grey |
| text-body | dark text | light text |
| text-body-secondary | muted text | muted light text |
| text-body-emphasis | strongest text | strongest light text |
| border (default color) | light grey border | dark grey border |
Subtle color variants also adapt: bg-primary-subtle, text-primary-emphasis and border-primary-subtle produce a tinted panel that stays readable in both modes:
<div class="p-3 rounded bg-success-subtle text-success-emphasis border border-success-subtle">
Deployment finished successfully.
</div>Bootstrap does not detect prefers-color-scheme by itself; you decide. The approach the Bootstrap docs use is a small inline script in <head> (so the page never flashes the wrong theme) that reads a stored choice and falls back to the operating system setting:
<script>
(() => {
const stored = localStorage.getItem("theme");
const system = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-bs-theme", stored && stored !== "auto" ? stored : system);
})();
</script>Listen for system changes so "auto" stays in sync while the page is open:
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if ((localStorage.getItem("theme") || "auto") === "auto") setTheme("auto");
});A dropdown with three options is the common pattern:
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Theme
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><button class="dropdown-item" data-theme-value="light">Light</button></li>
<li><button class="dropdown-item" data-theme-value="dark">Dark</button></li>
<li><button class="dropdown-item" data-theme-value="auto">Auto</button></li>
</ul>
</div>The script at the top of this lesson wires those buttons to localStorage and the attribute. Mark the current choice with .active and aria-pressed="true" for clarity.
Write custom styles against Bootstrap's variables so they follow the mode automatically:
.hero {
background-color: var(--bs-tertiary-bg);
color: var(--bs-body-color);
border: 1px solid var(--bs-border-color);
}When you need a value that has no variable, scope an override to the dark mode:
[data-bs-theme="dark"] .hero {
background-image: url("hero-dark.svg");
}In Sass, the color-mode() mixin generates that selector for you:
.hero {
background-image: url("hero-light.svg");
@include color-mode(dark) {
background-image: url("hero-dark.svg");
}
}Dark-mode values for the built-in variables live in the $*-dark Sass variables ($body-bg-dark, $primary-text-emphasis-dark and so on), which you override before importing Bootstrap.
Any attribute value works. Define a new mode by redefining the root variables under that selector:
[data-bs-theme="sepia"] {
color-scheme: light;
--bs-body-bg: #f4ecd8;
--bs-body-color: #4a3f2a;
--bs-tertiary-bg: #ece2c8;
--bs-border-color: #d9cdaa;
}color-scheme tells the browser which native controls (scrollbars, date pickers) to render.
<head> to avoid a flash of the wrong theme.bg-white / text-black. Replace with bg-body / text-body so surfaces flip..navbar-dark and .dropdown-menu-dark. They still function but are deprecated; use data-bs-theme="dark" on the component.How do you switch an entire page to Bootstrap's dark theme?
data-bs-theme="dark" on <html> (or any element) switches every component to dark mode via CSS variables.bg-body-*, text-body-*, *-subtle and *-emphasis utilities instead of fixed colors so surfaces adapt.prefers-color-scheme and persist the user's choice in localStorage with a script in <head>.--bs-* variables; scope exceptions with [data-bs-theme="dark"] or the color-mode() mixin.Next lesson: CSS Variables and Theming - restyle components at runtime with --bs-* custom properties and component-level variables.