Theming with CSS variables, RTL and colour modes
Retheme components without Sass, build a dark mode that actually covers your custom styles, and make the layout work in a right-to-left locale.
Component CSS variables
Every Bootstrap 5 component reads its colours and geometry from local custom properties, so a theme can be applied with plain CSS loaded after the framework — no build step, no Sass.
<style>
/* Component-scoped override: only affects buttons */
.btn-primary {
--bs-btn-bg: #6d28d9;
--bs-btn-border-color: #6d28d9;
--bs-btn-hover-bg: #5b21b6;
--bs-btn-hover-border-color: #5b21b6;
--bs-btn-active-bg: #4c1d95;
--bs-btn-focus-shadow-rgb: 109, 40, 217;
--bs-btn-color: #fff;
--bs-btn-hover-color: #fff;
}
/* Global override: the whole framework follows */
:root {
--bs-body-font-family: "Inter", system-ui, sans-serif;
--bs-border-radius: 0.5rem;
--bs-body-bg: #fbfbfd;
--bs-link-color-rgb: 109, 40, 217;
--bs-link-hover-color-rgb: 91, 33, 182;
}
</style>| Variable family | Example | Affects |
|---|---|---|
--bs-body-* | --bs-body-bg, --bs-body-color | Page background and default text |
--bs-secondary-color | — | Muted text such as text-body-secondary |
--bs-border-* | --bs-border-color | All component borders |
--bs-btn-* | --bs-btn-bg | A single button variant |
--bs-nav-link-* | --bs-nav-link-color | Nav and dropdown link states |
💡
A CSS-variable theme changes appearance but not the utility class list. If you also need new colours available as utilities such as
text-brand, that still requires the Sass utility API — a common surprise for teams that chose the no-build path.Light and dark colour modes
<html lang="en" data-bs-theme="light">
<head>
<!-- bootstrap.min.css includes every colour-mode rule -->
</head>
<body>
<!-- Layout that reflows and restyles itself for dark mode -->
<div class="card">
<div class="card-body">
<h2 class="card-title h6">Nightly report</h2>
<p class="text-body-secondary mb-0">Rendered 2 hours ago.</p>
</div>
</div>
<!-- Per-subtree override: the theme attribute works at any depth -->
<div data-bs-theme="dark" class="p-3 rounded">
<button class="btn btn-outline-light btn-sm">Always dark here</button>
</div>
<script>
const stored = localStorage.getItem('theme');
const preferred = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-bs-theme', stored || preferred);
document.getElementById('theme-toggle')?.addEventListener('click', () => {
const next = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-bs-theme', next);
localStorage.setItem('theme', next);
});
</script>data-bs-theme="auto"(the default on:root) follows the operating system; settinglightordarkpins it.- Custom CSS should consume the variables rather than hard-coding colours: write
var(--bs-body-bg), not#fff. - Set the attribute on
<html>in an inline script in the head to avoid a flash of the wrong theme before hydration. color-schemeis set by Bootstrap, which is what makes native form controls and scrollbars match.
Right-to-left and logical properties
<!-- One attribute flips the whole framework -->
<html lang="ar" dir="rtl" data-bs-theme="light">
<head>
<link rel="stylesheet" href="bootstrap.rtl.min.css">
</head>/* Your own CSS must use logical properties, or it will not flip. */
.panel {
/* wrong: pinned to the left forever */
/* margin-left: 1rem; border-left: 2px solid; */
/* right: follows the writing direction */
margin-inline-start: 1rem;
border-inline-start: 2px solid var(--bs-border-color);
}
/* Bootstrap's own directional utilities are direction-aware: */
/* ms-2 -> margin-inline-start; me-2 -> margin-inline-end */
/* text-start and text-end replace text-left and text-right */| LTR-era | Direction-aware | Meaning |
|---|---|---|
ml-3 / mr-3 | ms-3 / me-3 | Start and end of the writing direction |
pl-3 / pr-3 | ps-3 / pe-3 | Inline padding |
text-left / text-right | text-start / text-end | Alignment |
float-left | float-start | Floats |
border-left | border-start | Border side |
The RTL build is a separate stylesheet — importing bootstrap.min.css and flipping dir gives you mirrored text with a left-to-right layout underneath. Use bootstrap.rtl.min.css, or the Sass build with $enable-rtl if you customise.
FAQ
Can I theme Bootstrap without Sass?
For colour, radius, typography and most component geometry, yes: override the CSS custom properties after the framework stylesheet. You cannot add new utility classes or change breakpoints without Sass, since those generate classes at build time.
Why is part of my page still light in dark mode?
Something sets a literal colour. Replace
background: #fff with var(--bs-body-bg), use text-body-secondary instead of a fixed grey, and check utility classes such as bg-white which do not adapt.Related
Utilities and Sass customisation Accessibility with Bootstrap
Last refreshed 2026-09-18.