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 familyExampleAffects
--bs-body-*--bs-body-bg, --bs-body-colorPage background and default text
--bs-secondary-colorMuted text such as text-body-secondary
--bs-border-*--bs-border-colorAll component borders
--bs-btn-*--bs-btn-bgA single button variant
--bs-nav-link-*--bs-nav-link-colorNav 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; setting light or dark pins 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-scheme is 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-eraDirection-awareMeaning
ml-3 / mr-3ms-3 / me-3Start and end of the writing direction
pl-3 / pr-3ps-3 / pe-3Inline padding
text-left / text-righttext-start / text-endAlignment
float-leftfloat-startFloats
border-leftborder-startBorder 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.

Utilities and Sass customisation Accessibility with Bootstrap

Last refreshed 2026-09-18.