Colours & Dark Mode
Colour tells users what matters: green for success, red for errors, blue for the main action. Bootstrap gives you a small, consistent palette and classes to apply it to text, backgrounds and borders. Since version 5.3 it also has built-in colour modes, so a whole page or a single component can switch to a dark theme with one attribute.
In this lesson you will use the theme colours, the newer subtle and emphasis shades, and build a working dark mode toggle.
Theme colours
Bootstrap names its colours by purpose, not by appearance. That way, if a company's brand colour is orange, you change primary once and every primary button, link and badge follows.
| Name | Usual meaning |
|---|---|
primary | The main action or brand colour |
secondary | Less important actions |
success | Something worked: payment received, form saved |
danger | Errors and destructive actions such as Delete |
warning | Needs attention: low stock, unsaved changes |
info | Neutral information |
light and dark | Light grey and near-black surfaces |
Each name works with many prefixes: text-success, bg-danger, border-warning, btn-primary, alert-info and so on.
Text and background utilities
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Colours</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-3">
<h1 class="h4">Order #4521, Lucknow</h1>
<p class="text-success fw-semibold">Payment of ₹1,299 received.</p>
<p class="text-danger">One item is out of stock.</p>
<p class="text-body-secondary">Ordered on 28 October, 6:40 pm</p>
<div class="p-3 mb-2 text-bg-primary rounded">text-bg-primary: background with a readable text colour</div>
<div class="p-3 mb-2 text-bg-warning rounded">text-bg-warning: dark text chosen automatically</div>
<div class="p-3 mb-2 bg-success-subtle text-success-emphasis border border-success-subtle rounded">
Subtle background, emphasis text and subtle border: a calm success message
</div>
<div class="p-3 mb-2 bg-danger bg-opacity-25 rounded">bg-danger at 25% opacity</div>
<div class="p-3 bg-body-tertiary rounded">bg-body-tertiary: a soft panel colour</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>The main families of colour classes:
text-{color}andbg-{color}set only the text or only the background. Withbg-primaryyou must also pick a text colour yourself.text-bg-{color}sets the background and a contrasting text colour in one class. It is the safest choice for coloured boxes and badges.bg-{color}-subtle,text-{color}-emphasisandborder-{color}-subtle(new in 5.3) give pale backgrounds and darker text that look softer than the full colour. They also adapt automatically in dark mode.text-body,text-body-secondary,text-body-tertiaryandtext-body-emphasisare the page's own text colours, from normal to faint to strongest.bg-body,bg-body-secondaryandbg-body-tertiaryare the page's own surface colours, good for panels and sidebars.bg-opacity-{10|25|50|75|100}andtext-opacity-{25|50|75|100}fade a colour.
There are also link colour classes such as link-primary, link-body-emphasis and link-underline-opacity-25 for styling links.
Colour modes and data-bs-theme
Bootstrap 5.3 stores its colours in CSS variables (custom properties such as --bs-body-bg and --bs-body-color; see CSS Custom Properties). A colour mode is a different set of values for those variables. Bootstrap ships two modes, light (the default) and dark, and you choose one with the data-bs-theme attribute:
<!-- Whole page in dark mode -->
<html lang="en" data-bs-theme="dark">
<!-- Only one part of the page in dark mode -->
<div class="card" data-bs-theme="dark"> ... </div>
<!-- A light island inside a dark page -->
<div data-bs-theme="light"> ... </div>The attribute works on any element, and everything inside it follows. Body text, backgrounds, borders, form fields, cards, dropdowns, modals and tables all switch together because they read the same variables. You do not write any dark mode CSS yourself.
A light/dark toggle with JavaScript
Users like a switch that remembers their choice. The plan is: on page load, use the saved choice if there is one, otherwise follow the operating system setting (prefers-color-scheme); when the button is clicked, flip the mode and save it in localStorage.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dark mode toggle</title>
<script>
// Runs before the page is drawn, so there is no flash of the wrong theme.
const saved = localStorage.getItem("theme");
const systemDark = matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.setAttribute("data-bs-theme", saved ?? (systemDark ? "dark" : "light"));
</script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Isha's Study Planner</h1>
<button type="button" class="btn btn-outline-secondary btn-sm" id="themeBtn">Switch theme</button>
</div>
<div class="p-3 bg-body-tertiary border rounded mb-3">
<p class="mb-1 text-body-emphasis fw-semibold">Today</p>
<p class="mb-0 text-body-secondary">Physics revision, 2 hours</p>
</div>
<div class="p-3 bg-success-subtle text-success-emphasis rounded mb-3">Chemistry chapter 4 complete</div>
<input class="form-control" placeholder="Add a task">
<p class="mt-3 small">Current theme: <span id="current"></span></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const root = document.documentElement;
const current = document.getElementById("current");
current.textContent = root.getAttribute("data-bs-theme");
document.getElementById("themeBtn").addEventListener("click", () => {
const next = root.getAttribute("data-bs-theme") === "dark" ? "light" : "dark";
root.setAttribute("data-bs-theme", next);
localStorage.setItem("theme", next);
current.textContent = next;
});
</script>
</body>
</html>Click the button: the page, the panels, the success message and even the input field change together. Run the example again and the page remembers your last choice. Two details are worth copying into your own projects:
- The small script in the
<head>sets the attribute before the page is drawn. If you set it at the end of the body, users with dark mode see a white flash on every page load. saved ?? (...)uses the nullish operator: if nothing is saved,getItemreturnsnulland the system setting is used instead.
One limitation in 5.3: the plain text-{color} and bg-{color} classes keep the same colour in both modes, while the -subtle, -emphasis and body classes adapt. For dark-mode-friendly designs, prefer the adapting ones.
Older code
Before 5.3 there were no colour modes, and you will meet these older patterns:
text-mutedis deprecated since 5.3; usetext-body-secondary. It still works for now but is planned for removal in Bootstrap 6.text-black-50andtext-white-50are deprecated since 5.1; use a colour withtext-opacity-50.navbar-dark,dropdown-menu-dark,carousel-darkandbtn-close-whitewere the per-component way to darken things. In 5.3,data-bs-theme="dark"on the component is preferred for navbars, dropdowns, carousels and close buttons.- Bootstrap 4 badges used
badge-primary. In Bootstrap 5 you writetext-bg-primaryon the badge.
Common mistakes
bg-darkwithout a text colour. The text stays dark on a dark background. Usetext-bg-darkinstead.- Setting the theme in a script at the bottom of the page. Users see a flash of the light theme. Set
data-bs-themein the<head>. - Hard-coding colours like
style="background:#fff". That white box stays white in dark mode. Usebg-bodyorbg-body-tertiary, which adapt. - Using
text-primaryfor long paragraphs. Coloured body text is tiring and often low contrast. Keep colour for short labels and actions.
Key points
- Theme colours are named by purpose:
primary,secondary,success,danger,warning,info,light,dark. text-bg-{color}sets a background plus a readable text colour;-subtleand-emphasisclasses give softer shades.body,body-secondaryandbody-tertiaryclasses use the page's own colours and adapt to dark mode.data-bs-theme="dark"on<html>or any element switches it to the dark colour mode.- A theme toggle sets the attribute in the
<head>, followsprefers-color-schemeby default and saves the choice inlocalStorage.
Exercise
Build a small "Bus pass status" page for Rohtak city buses. Show three status boxes: an active pass (success, subtle style), a pass expiring in 3 days (warning, using text-bg-warning) and a blocked pass (danger, subtle style), each with a short sentence. Add a button that switches between light and dark mode and remembers the choice. Check that all three boxes stay readable in both modes.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bus pass status</title>
<script>
const saved = localStorage.getItem("theme");
const dark = matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.setAttribute("data-bs-theme", saved ?? (dark ? "dark" : "light"));
</script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<button type="button" class="btn btn-secondary btn-sm mb-3" id="toggle">Light / dark</button>
<div class="p-3 mb-2 rounded bg-success-subtle text-success-emphasis">Aarav: pass active until 30 November</div>
<div class="p-3 mb-2 rounded text-bg-warning">Priya: pass expires in 3 days</div>
<div class="p-3 rounded bg-danger-subtle text-danger-emphasis">Rohan: pass blocked, visit the depot</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById("toggle").addEventListener("click", () => {
const root = document.documentElement;
const next = root.getAttribute("data-bs-theme") === "dark" ? "light" : "dark";
root.setAttribute("data-bs-theme", next);
localStorage.setItem("theme", next);
});
</script>
</body>
</html>The subtle and emphasis classes switch shades in dark mode automatically, and text-bg-warning always pairs yellow with dark text, so every box stays readable.
Quick Quiz
1. Which class gives a red background with a text colour that is readable on it?
text-bg-{color} sets both the background and a contrasting text colour.
2. How do you switch one card, and only that card, to dark mode in Bootstrap 5.3?
The data-bs-theme attribute works on any element and applies to everything inside it.
3. Which class replaces the deprecated text-muted?
Since 5.3, text-body-secondary uses the colour-mode-aware secondary text colour.
4. Why is the theme set by a script in the <head> instead of at the end of the <body>?
Setting the attribute before the page is drawn means the first paint already uses the right colours.
Learn Bootstrap with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
