Bootstrap 5.3’s data-bs-theme attribute together with CSS custom properties, a persistent JavaScript toggle, and the considerations you need to apply when working inside a premium HTML template such as the Canvas HTML Template.
Key Takeaways
- Bootstrap 5.3 introduced native dark mode via the
data-bs-theme="dark"attribute — no extra stylesheet required. - Applying the attribute to
<html>scopes dark mode globally; applying it to a component scopes it locally. - CSS custom properties (
--bs-body-bg,--bs-body-color, etc.) do the heavy lifting — override them with--cnvs-themecolorand template-specific variables to match your brand. - A JavaScript toggle that reads and writes to
localStorageis the only reliable way to persist the user’s preference across page loads. - The OS-level
prefers-color-schememedia query should seed the default, not override an explicit user choice. - Images, SVGs, and custom plugin styles require separate, explicit dark-mode handling.
- Never rely solely on a CSS class swap —
data-bs-themeis the sanctioned API and the only one Bootstrap’s own JavaScript respects.
Why data-bs-theme Is the Right API
Before Bootstrap 5.3 (released June 2023), dark mode meant shipping a parallel stylesheet, duplicating dozens of component overrides, and hoping nothing drifted out of sync. Bootstrap 5.3 replaced that pattern with a theme attribute system. Set data-bs-theme="dark" on any element and Bootstrap’s bundled CSS — via a cascade of CSS custom properties — recolours every component inside that element automatically.
The two supported values are light and dark. You can omit the attribute entirely and the browser default (light) applies. Critically, this is not a JavaScript-only feature: the attribute drives pure CSS, so it works even before any script executes, eliminating the flash of unstyled content that plagues class-based implementations.
<!-- Global dark mode — applies to every element on the page -->
<html lang="en" data-bs-theme="dark">
<!-- Scoped dark mode — only this card is dark, page stays light -->
<div class="card" data-bs-theme="dark">
<div class="card-body">Dark card on a light page</div>
</div>Scoped theming is genuinely useful for contrast sections, pricing tables, and sidebars. It removes the need for custom colour utilities that previously had to be written by hand. For a detailed look at how Bootstrap’s variable system compares to SCSS-based approaches, see our post on SCSS vs CSS variables for theming Bootstrap 5.

Seeding the Default from prefers-color-scheme
Respecting the operating system preference is good practice, but it must be treated as a default, not a permanent override. The correct sequence is:
- On first visit, read
prefers-color-schemeto set an initial value. - Store the user’s explicit choice in
localStorage. - On every subsequent visit, prefer
localStorageover the OS value.
Apply the attribute synchronously in a <script> tag inside <head> — before any CSS renders — to prevent a colour flash:
<head>
<meta charset="UTF-8">
<script>
(function () {
var stored = localStorage.getItem('bs-theme');
if (stored) {
document.documentElement.setAttribute('data-bs-theme', stored);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
}
})();
</script>
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>Placing this before the stylesheet link is intentional. The attribute must exist on <html> before the cascade evaluates it, otherwise the browser paints once in light mode and then repaints in dark, producing a visible flicker.
Building a Persistent Toggle Button
A toggle that updates both the DOM attribute and localStorage in one step is all you need. Keep it framework-agnostic so it works in any HTML template without jQuery or a build step:
<button id="theme-toggle" class="btn btn-outline-secondary btn-sm" aria-label="Toggle dark mode">
Dark Mode
</button>
<script>
var btn = document.getElementById('theme-toggle');
function applyTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('bs-theme', theme);
btn.textContent = theme === 'dark' ? 'Light Mode' : 'Dark Mode';
}
// Sync button label on load
applyTheme(document.documentElement.getAttribute('data-bs-theme') || 'light');
btn.addEventListener('click', function () {
var current = document.documentElement.getAttribute('data-bs-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
});
</script>If your project uses Canvas’s navbar, this button slots naturally into the header utility area. Check out the patterns discussed in 10 Bootstrap 5 navbar styles for placement ideas that keep the toggle accessible without cluttering the primary navigation.

Overriding CSS Custom Properties for Template-Specific Colours
Bootstrap’s dark palette is functional but generic. In a template like Canvas, the brand accent is exposed as --cnvs-themecolor. When you activate dark mode, that variable does not change automatically — you must define dark-mode overrides in your own stylesheet:
<style>
/ Canvas theme colour — light default /
:root {
--cnvs-themecolor: #1abc9c;
--cnvs-themecolor-rgb: 26, 188, 156;
}
/ Dark mode overrides /
[data-bs-theme="dark"] {
--bs-body-bg: #121212;
--bs-body-color: #e0e0e0;
--bs-border-color: #2c2c2c;
--cnvs-themecolor: #1de9b6; / slightly brighter for dark backgrounds /
}
</style>Scope your overrides to [data-bs-theme="dark"], not to a class like .dark-mode. Using the attribute selector keeps you aligned with Bootstrap’s own cascade and avoids specificity conflicts with component styles.
For components that use inline background colours or RGBA values derived from --bs-body-bg-rgb, you will need to update those RGB triplets as well. Check the compiled Bootstrap CSS for every variable that Bootstrap’s dark palette redefines — there are roughly 40 of them in 5.3.x — and decide which ones your design requires you to override further.
Handling Images, SVGs, and Third-Party Plugins
CSS custom properties do not reach bitmap images. A white logo on a dark background that becomes invisible in light mode is one of the most common oversights. Three practical solutions:
- CSS filter:
[data-bs-theme="dark"] .logo-img { filter: invert(1) brightness(1.2); }— fast but crude; avoid on photographs. - Separate image sources: Use a
<picture>element with themediaattribute, or swap thesrcvia JavaScript when the theme changes. - SVG inline fills: Replace hard-coded
fill="#000"values withfill="currentColor"so the SVG inherits--bs-body-colorautomatically.
Third-party plugins (charts, maps, code highlighters) maintain their own colour systems. You must check each plugin’s documentation for a dark theme option and initialise it conditionally based on the current value of data-bs-theme. Reading the attribute at initialisation time and re-initialising on toggle events is the cleanest pattern.
Performance implications are worth noting here too. Dark mode does not meaningfully affect Core Web Vitals on its own, but the image-swap strategy you choose can. For a full treatment of image optimisation in Bootstrap 5 projects, see how to optimise Bootstrap 5 images for Core Web Vitals.
When Dark Mode Is the Wrong Priority
Dark mode adds real implementation surface area. Before committing to it, consider:
- Content-heavy editorial sites see lower dark-mode adoption rates than developer tools or dashboards. Measure your audience before investing.
- Tight deadlines: If you are shipping a landing page in 48 hours, Bootstrap’s default dark palette is good enough — ship it, then refine the colour overrides in a follow-up.
- Legacy browsers:
data-bs-themerelies on CSS custom properties, which are unsupported in Internet Explorer. If IE support is a genuine requirement, the attribute-based approach will not work without a polyfill. - High-contrast accessibility requirements: Dark mode and high-contrast mode are not the same thing. WCAG 1.4.3 contrast ratios must be verified independently for both light and dark palettes.
Frequently Asked Questions
Yes — if you hard-code data-bs-theme="dark" on the <html> element in your HTML file, every Bootstrap component on the page will render in dark mode with no JavaScript at all. JavaScript is only needed if you want a runtime toggle or want to seed the theme from localStorage or prefers-color-scheme.
Bootstrap 5.3’s component styles are written to respond to the [data-bs-theme="dark"] attribute selector — not to any class. Using a class like .dark-mode instead means you must manually duplicate every component override that Bootstrap already provides for free through the attribute. The attribute is the sanctioned API; the class approach requires significantly more maintenance.
Yes. Add data-bs-theme="dark" to any element — a <section>, <div>, or individual component. Bootstrap’s CSS custom properties cascade from that element downward, so only its children are recoloured. The rest of the page remains in light mode. This is ideal for hero sections, pricing tables, or footer areas that call for a dark background without changing the entire page.
Canvas exposes its accent colour as --cnvs-themecolor and related variables. Add a [data-bs-theme="dark"] block to your custom stylesheet that overrides both Bootstrap’s core colour variables and any Canvas-specific variables you need to adjust. Run the synchronous <head> script described in this post to prevent a light-flash on load, and test every Canvas section — sliders, portfolio grids, pricing blocks — individually, since some use inline background utilities that require explicit overrides.
The attribute-based approach has negligible impact on Largest Contentful Paint or Cumulative Layout Shift, provided you apply the data-bs-theme attribute synchronously before CSS renders. The main risk is the image-swap strategy: swapping src attributes after load can trigger additional network requests and affect LCP if the swapped image is the page’s largest element. Prefer CSS filters or inline SVGs with currentColor for logo-level assets to avoid this.
Looking for a production-ready Bootstrap 5 HTML template? Browse Canvas Template demos and find the perfect starting point for your next project.
If you’re building with the Canvas HTML Template and want to ship production-ready Bootstrap 5 layouts faster, try Canvas Builder free — the visual builder that exports clean Canvas-ready markup in minutes.
Skip the setup — build it free
Spin up a complete Bootstrap 5 site, blog included, with Canvas Builder. No coding, no cost.
Canvas Team
Tutorials and tips for building beautiful Bootstrap 5 websites with the Canvas HTML Template and Canvas Builder.
More from the Canvas Blog