π¨ Customizing Bootstrap
The classic complaint about Bootstrap β "every site looks the same" β is only true if you stop at the defaults. This lesson climbs a ladder of customization, from a two-line CSS-variable override to a fully-themed Sass build, so your project keeps Bootstrap's power while wearing its own identity.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Choose the right level of customization for a project β plain CSS, CSS variables, or Sass
- Override Bootstrap 5's CSS custom properties (
--bs-*) without a build step - Retheme colors, typography, and components by setting Sass variables before importing Bootstrap
- Add custom theme colors to generate new
.btn-*/.text-*/.bg-*classes - Trim file size with selective imports and understand a basic Sass build pipeline
Estimated Time: 40β50 minutes β’ Difficulty: Intermediate
Hands-on: Rebrand a card and button using only CSS-variable overrides, then plan the Sass equivalent.
In This Lesson
Why Customize?
Bootstrap gives you a fast, consistent foundation. Customizing it lets you keep that speed while achieving four goals:
- Brand fit β match your organization's colors, fonts, and shapes.
- Distinctiveness β stop looking like the default template.
- Focus β include only the components and utilities you actually use.
- Performance β smaller CSS by dropping what you don't need.
π‘ Analogy β a house you finish: Bootstrap is the structural shell: framing, plumbing, wiring. Customization is the paint, the fixtures, and the furniture that make the house recognizably yours. You rarely rebuild the frame β you decorate within it.
The Three Levels
Customization is a ladder. Each rung is more powerful and more involved than the last β pick the lowest rung that meets your need.
quick, no build"] --> B["Level 2: CSS variable overrides
--bs-* at runtime, no build"] B --> C["Level 3: Sass customization
full control, needs a build"]
| Level | Effort | Best for |
|---|---|---|
| Plain CSS overrides | Lowest β one extra stylesheet | Prototypes, tiny visual tweaks |
CSS variables (--bs-*) | Low β a :root block, no tooling | Moderate rebrands without a build step |
| Sass variables | Highest β Node + a Sass compiler | Deep theming, custom colors, optimized builds |
Level 1 & 2: CSS Overrides & Variables
Level 1 β plain CSS after Bootstrap
Load your own stylesheet after Bootstrap's and override specific rules. Simple, but you fight the cascade and specificity, and it doesn't scale well.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="custom.css" rel="stylesheet">
/* custom.css */
.btn-primary {
background-color: #8e44ad;
border-color: #8e44ad;
}
.btn-primary:hover {
background-color: #732d91;
border-color: #732d91;
}
Level 2 β override the --bs-* variables
Bootstrap 5 exposes its design tokens as CSS custom properties. Overriding them in a :root block re-skins components at runtime β no compiler required. This is the sweet spot for many projects.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--bs-primary: #6200ee;
--bs-primary-rgb: 98, 0, 238; /* keep the RGB triplet in sync */
--bs-body-font-family: 'Poppins', system-ui, sans-serif;
--bs-border-radius: 0.5rem;
}
</style>
β οΈ The -rgb gotcha
Many Bootstrap components build translucent backgrounds with rgba(var(--bs-primary-rgb), .5). If you change --bs-primary but forget --bs-primary-rgb, those transparent variants keep the old color. Always update both together.
π‘ Variables reach where plain overrides can't
Setting --bs-primary cascades to every component that reads it β buttons, links, form focus rings, progress bars β in one line. A plain-CSS approach would need a separate rule for each.
Level 3: Sass Customization
For full control you customize Bootstrap's Sass source. You set variables before importing Bootstrap, so the framework compiles with your values baked in β this can do things CSS variables can't, like generating brand-new utility classes.
First install the source:
npm install bootstrap
Then create a custom.scss that declares overrides, then imports Bootstrap:
// custom.scss
// 1. Your variable overrides come FIRST
$primary: #8e44ad;
$secondary: #2c3e50;
$success: #27ae60;
$font-family-sans-serif: 'Poppins', system-ui, sans-serif;
$border-radius: 0.5rem;
$enable-shadows: true;
// 2. Required Bootstrap layers
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
// 3. Only the components you need
@import "bootstrap/scss/grid";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/card";
@import "bootstrap/scss/forms";
// 4. Utilities API
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
// 5. Your own extra styles last
.custom-panel {
padding: 1rem;
border-left: 4px solid $primary;
}
$primary: #8e44ad"] --> B["Import Bootstrap source"] B --> C["Select components
@import buttons, cardβ¦"] C --> D["Add custom styles"] D --> E["Sass compiler"] E --> F["Final custom CSS"]
π Order matters
Sass variable overrides only take effect if they appear before @import "bootstrap/scss/variables". That file uses !default on every variable, meaning "use this value unless one is already set" β so your earlier definition wins.
Theming Colors
Bootstrap's color system is semantic: components reference names like primary and danger, not raw hex values. Reassign those names and the whole UI shifts together.
// Bootstrap's theme colors (defaults shown)
$primary: #0d6efd;
$secondary: #6c757d;
$success: #198754;
$info: #0dcaf0;
$warning: #ffc107;
$danger: #dc3545;
$light: #f8f9fa;
$dark: #212529;
Swap them for a fictional finance app's palette:
$primary: #1e40af; // deep blue β trust
$success: #15803d; // green β gains
$danger: #b91c1c; // red β losses
$warning: #d97706; // amber β caution
Adding brand-new theme colors
This is the trick CSS variables can't do: merge extra colors into the $theme-colors map and Bootstrap generates a full set of classes β .btn-profit, .text-loss, .bg-neutral β for each.
$custom-colors: (
"profit": #059669,
"loss": #e11d48,
"neutral": #a1a1aa
);
// Merge AFTER importing functions/variables/maps
$theme-colors: map-merge($theme-colors, $custom-colors);
β Mind color contrast
When you pick brand colors, keep text readable. Bootstrap's color-contrast() function automatically chooses light or dark text for a given background, so buttons stay legible β but always verify against a contrast checker for accessibility (WCAG AA needs 4.5:1 for body text).
Theming Typography & Components
Typography
Bootstrap defaults to a native system font stack. Point the font variables at a web font (loaded in your HTML) and set the type scale to match your brand.
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
$font-family-sans-serif: 'Poppins', system-ui, sans-serif;
$headings-font-family: 'Playfair Display', Georgia, serif;
$font-size-base: 1rem; // ~16px
$h1-font-size: $font-size-base * 2.5;
$h2-font-size: $font-size-base * 2;
$headings-font-weight: 700;
$line-height-base: 1.6;
Component variables
Almost every component exposes its own Sass variables. A few examples:
// Buttons β pill-shaped, roomy
$btn-padding-y: 0.75rem;
$btn-padding-x: 2rem;
$btn-font-weight: 600;
$btn-border-radius: 2rem;
// Cards β borderless with a soft shadow
$card-border-width: 0;
$card-border-radius: 1rem;
$card-box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
// Global switches that ripple across many components
$border-radius: 0.5rem;
$enable-shadows: true;
$enable-gradients: false;
Because these feed the same components everywhere, one change β say a rounder $border-radius β updates buttons, cards, inputs, and alerts consistently.
Selective Imports & Build
Ship only what you use
The full Bootstrap CSS is around 190 KB minified. If your project only uses the grid, buttons, cards, and forms, import just those layers and cut the bundle dramatically.
The selective import list from Level 3 is exactly this optimization β leave out modal, carousel, or accordion if you never use them.
A minimal build pipeline
Sass has to be compiled to CSS. A tiny package.json script is enough to start:
{
"scripts": {
"css": "sass scss/custom.scss dist/custom.css",
"css:watch": "sass --watch scss/custom.scss dist/custom.css",
"css:build": "sass scss/custom.scss dist/custom.min.css --style compressed"
},
"devDependencies": {
"bootstrap": "^5.3.3",
"sass": "^1.77.0"
}
}
Run npm run css:watch while developing to recompile on every save, and npm run css:build to produce a minified file for production. For larger apps you'd add autoprefixer (browser prefixes) and a bundler like Vite or webpack, but the compile-and-minify core stays the same.
π‘ The Dart Sass module system
Newer Sass deprecates @import in favor of @use and @forward. Bootstrap 5.3 still documents @import, and it works today, but when you start a fresh project check Bootstrap's current docs for the @use pattern so you're not building on a deprecated feature.
Hands-on: Rebrand a Card
ποΈ Re-skin Bootstrap with CSS variables
Objective: Using Level 2 (CSS variables only, no build step), change Bootstrap's primary color to a teal brand color and round every corner more, then confirm a card and a primary button both pick up the change.
Starter:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="container py-4">
<div class="card p-3" style="max-width: 22rem;">
<h5 class="card-title">Brand test</h5>
<p class="card-text">Does this card follow my brand color?</p>
<button class="btn btn-primary">Primary action</button>
</div>
</div>
Instructions:
- Add a
<style>block with a:rootoverride after the Bootstrap link. - Set
--bs-primaryto#0d9488(teal) β and remember its-rgbpartner. - Increase
--bs-border-radiusso cards and buttons look rounder. - Reload and check the primary button changed color.
- Stretch: write the equivalent Sass override and note why it can do things CSS variables can't (hint: new theme colors).
π‘ Hint
The teal #0d9488 is RGB 13, 148, 136. Set both --bs-primary and --bs-primary-rgb or translucent variants keep the default blue. Border radius is --bs-border-radius.
β Solution
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--bs-primary: #0d9488;
--bs-primary-rgb: 13, 148, 136;
--bs-border-radius: 0.75rem;
--bs-border-radius-lg: 1rem;
}
</style>
The .btn-primary now renders teal and both the card and button have rounder corners β with no compiler in sight.
Sass equivalent (needs a build) β and its superpower, a new theme color:
$primary: #0d9488;
$border-radius: 0.75rem;
// Something CSS variables CANNOT do:
$theme-colors: map-merge($theme-colors, ("brand": #0d9488));
// -> generates .btn-brand, .text-brand, .bg-brand, etc.
@import "bootstrap/scss/bootstrap";
CSS variables re-skin existing classes; Sass can additionally generate new ones, because the utility and component classes are produced at compile time from the maps.
π― Quick Quiz
Question 1: You want a moderate rebrand but can't add a build step. Which level fits best?
Question 2: Why must Sass variable overrides appear before @import-ing Bootstrap's variables?
Question 3: Which task genuinely requires Sass rather than CSS-variable overrides?
Summary & Quiz
π Key Takeaways
- Customization is a ladder: plain CSS β CSS variables β Sass. Use the lowest rung that works.
- CSS variable overrides (
--bs-*) re-skin components at runtime with no build β just remember the-rgbpartners. - Sass gives full control: set variables before importing Bootstrap (they win via
!default). - Bootstrap's colors are semantic; reassign
$primaryand friends, or merge new colors into$theme-colorsto generate whole new class sets. - Selective imports plus a small Sass build ship only the CSS you actually use.
π Further Reading
π What's Next?
You've now seen both major CSS frameworks in this module. Next we switch philosophies entirely: Tailwind CSS Workflow and Setup shows how a utility-first framework handles configuration, theming, and production builds β a useful contrast to Bootstrap's component-first approach.
π Well done!
Your Bootstrap sites no longer have to look like Bootstrap. You can rebrand in two lines or theme every token β and you know which to reach for.