Skip to main content

🎨 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.

flowchart TD A["Level 1: Plain CSS overrides
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"]
LevelEffortBest for
Plain CSS overridesLowest β€” one extra stylesheetPrototypes, tiny visual tweaks
CSS variables (--bs-*)Low β€” a :root block, no toolingModerate rebrands without a build step
Sass variablesHighest β€” Node + a Sass compilerDeep 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;
}
flowchart TD A["Override variables
$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.

pie showData title CSS size (KB, approx.) "Trimmed to what you use" : 70 "Bytes you can drop" : 120

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:

  1. Add a <style> block with a :root override after the Bootstrap link.
  2. Set --bs-primary to #0d9488 (teal) β€” and remember its -rgb partner.
  3. Increase --bs-border-radius so cards and buttons look rounder.
  4. Reload and check the primary button changed color.
  5. 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 -rgb partners.
  • Sass gives full control: set variables before importing Bootstrap (they win via !default).
  • Bootstrap's colors are semantic; reassign $primary and friends, or merge new colors into $theme-colors to 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.