Overview
Every project so far has used Bootstrap exactly as the CDN ships it — the same blue primary color, the same system font, the same corner radius as every other site using the same CDN link. Bootstrap is actually distributed as Sass source, not just compiled CSS, specifically so that a real project never has to look like every other Bootstrap site. Every visual choice Bootstrap makes — the primary color, the base font, how rounded a button's corners are — is a Sass variable with a `!default` value, meaning your own value wins as long as it is set before Bootstrap's own Sass runs.
This project builds a small custom.scss file that overrides a handful of Bootstrap's core variables — `$primary`, `$secondary`, `$body-bg`, `$font-family-base`, `$border-radius` — before importing Bootstrap's own Sass, compiles that file to real CSS with the Sass CLI, and applies the result to a small branded demo page (a navbar and a row of cards) built from nothing but stock Bootstrap classes, now automatically reskinned by the variables you set.
- A `custom.scss` file that overrides `$primary`, `$secondary`, `$body-bg`, `$font-family-base`, and `$border-radius` before importing Bootstrap.
- A small custom Sass rule of your own (`.text-gradient`) that reuses the same brand variables Bootstrap's components now use.
- The Sass CLI command that compiles `custom.scss` into a real, deployable `custom.css`.
- A branded demo page — navbar and cards — built from ordinary Bootstrap classes that now render in your own colors, font, and corner radius automatically.
- An understanding of exactly which `<link>` a real project swaps to go from the CDN look to your fully custom-branded one.
Prerequisites
- Comfort with the `navbar`, `btn`, and `card` component classes used in the earlier three projects.
- Basic command-line familiarity — running `npm install` and one Sass CLI command.
- Understanding that Bootstrap ships as Sass source (`.scss` files) in addition to pre-compiled CSS, and that the CDN `<link>` used in every earlier project is only the pre-compiled, un-customized version.
- No prior Sass syntax knowledge required — variables, comments, and a single `@import` are the only Sass features this project touches.
Project Structure
A real version of this project has three moving pieces: `node_modules/bootstrap/scss/`, installed via `npm install bootstrap`, which holds every one of Bootstrap's own `.scss` partials; `custom.scss`, a file you write yourself that sets your variable overrides and then imports Bootstrap's Sass; and `custom.css`, the compiled output the Sass CLI produces from `custom.scss` — this is the file a real project's `<head>` links to instead of the CDN `bootstrap.min.css`.
The critical rule, and the one thing this whole project hinges on, is ordering: every variable override must be written before the `@import "bootstrap/scss/bootstrap";` line. Bootstrap's own Sass defines each variable with `!default`, a Sass flag meaning "only use this value if nothing has set the variable already." An override placed after the import is too late — the variable is already defined by the time your line runs, so `!default` silently ignores it.
Step 1: Install Bootstrap's Sass Source
`npm install bootstrap` pulls down Bootstrap's full package, including its `.scss` source files under `node_modules/bootstrap/scss/` — the same source the CDN's `bootstrap.min.css` was itself compiled from. The `sass` package is Dart Sass, the actual compiler that turns `.scss` files into `.css` files; it is a dev dependency since it is only needed while building, not at runtime in the browser.
# Installs Bootstrap's package, including its .scss source under node_modules/bootstrap/scssnpm install bootstrap@5.3.3
# Installs the Dart Sass compiler as a dev dependency - this is what turns .scss into real .cssnpm install --save-dev sass
# Create the file that will hold your variable overrides and the Bootstrap importtouch custom.scssStep 2: Override Bootstrap's Core Variables Before Importing
Every line below sets one Bootstrap variable to a new value — a violet brand color instead of Bootstrap's default blue, an amber accent, a light violet-tinted background instead of white, a custom font stack, and a larger corner radius applied sitewide. None of this is new CSS; it is Sass variables that Bootstrap's own component styles already reference internally, which is exactly why one small block of overrides reskins dozens of components at once.
// custom.scss
// Every variable below MUST be set before the @import line further down -// Bootstrap's own Sass only applies a variable's *default* value if nothing// has already defined it (that's what "!default" means inside Bootstrap's// own source), so these overrides only take effect because they run first
$primary: #7c3aed; // Violet brand color - replaces Bootstrap's default blue in .btn-primary, .text-primary, .bg-primary, links, focus rings, everywhere$secondary: #f59e0b; // Amber accent - replaces the default gray secondary color$body-bg: #faf5ff; // Very light violet-tinted background instead of white$font-family-base: "Poppins", system-ui, sans-serif; // Swaps Bootstrap's default system font stack for a brand font$border-radius: 0.75rem; // Rounder corners sitewide - buttons, inputs, alerts, and modals all read this one variable$border-radius-lg: 1rem; // A separate, slightly larger radius used by bigger components like cards$enable-shadows: true; // Turns on Bootstrap's built-in shadow utility classes, which are off by default
// This single import pulls in every Bootstrap component's Sass - each one// picks up the overridden variables above instead of its own built-in defaults@import "bootstrap/scss/bootstrap";Step 3: Add a Custom Class on Top of Bootstrap's Sass
Because this rule is appended after the `@import`, it can reuse `$primary` and `$secondary` directly — the same two variables Bootstrap's own buttons now use — instead of hardcoding the hex codes a second time. If the brand color is ever changed in Step 2, this gradient updates automatically the next time the file is compiled, with no second edit required.
// Appended to custom.scss, AFTER the @import "bootstrap/scss/bootstrap" line,// so it can use the same $primary/$secondary variables Bootstrap itself now uses.text-gradient { background: linear-gradient(90deg, $primary, $secondary); // reuses the brand variables instead of hardcoding hex codes again -webkit-background-clip: text; background-clip: text; color: transparent;}Step 4: Compile the Sass to CSS
The Sass CLI reads `custom.scss` — variable overrides, the Bootstrap import, and the custom `.text-gradient` rule — and writes out one plain `custom.css` file containing all of Bootstrap's compiled component styles with the overridden values baked directly into them. `--watch` keeps the compiler running in the background and recompiles automatically every time the file is saved, which is the normal way to work with Sass during development; a production build drops `--watch` and adds `--style=compressed` to minify the output.
# --watch keeps sass running, recompiling custom.scss -> custom.css every# time the file is saved - the normal Sass development workflownpx sass --watch custom.scss:custom.css
# For a one-off production build (no watching), drop --watch and minify the outputnpx sass custom.scss:custom.css --style=compressedStep 5: Build the Themed Demo Page Markup
This markup is deliberately ordinary — a `navbar`, `btn-primary`, `btn-secondary`, and a `row` of `card`s, the exact same classes used throughout the earlier three projects. `.text-gradient` is the only class here that did not ship with Bootstrap; everything else renders differently purely because of the variable overrides from Step 2, not because any class name changed.
<nav class="navbar navbar-expand-lg bg-white shadow-sm"> <div class="container"> <!-- text-gradient is the one custom class from Step 3 - everything else on this page is stock Bootstrap, reskinned automatically by custom.css --> <a class="navbar-brand fw-bold text-gradient" href="#">Lumen Studio</a> <div class="ms-auto"> <!-- btn-primary now renders violet, not Bootstrap's default blue, purely because $primary was overridden before Bootstrap's Sass compiled --> <button class="btn btn-primary">Get Started</button> </div> </div></nav>
<div class="container py-5"> <div class="row g-4"> <div class="col-md-4"> <div class="card h-100"> <!-- card corners now use $border-radius-lg from Step 2 automatically --> <div class="card-body"> <h3 class="h5 card-title">Design Tokens</h3> <p class="card-text">One set of Sass variables drives every component's color and shape.</p> <!-- btn-secondary now renders amber, not gray, from the same override --> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> <div class="col-md-4"> <div class="card h-100"> <div class="card-body"> <h3 class="h5 card-title">One Source of Truth</h3> <p class="card-text">Change a color once in custom.scss, not across dozens of hardcoded hex values.</p> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> <div class="col-md-4"> <div class="card h-100"> <div class="card-body"> <h3 class="h5 card-title">Still Just Bootstrap</h3> <p class="card-text">Every class here is stock Bootstrap - only the compiled variables underneath changed.</p> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> </div></div>Step 6: Link the Compiled CSS Instead of the CDN File
This is the one-line change that switches an entire page from Bootstrap's stock look to the fully custom-branded one: swap the CDN `<link>` used in every earlier project for a `<link>` pointing at the `custom.css` file compiled in Step 4. Nothing about the JavaScript bundle changes — Sass only ever touches CSS, so the same CDN `<script>` tag for `bootstrap.bundle.min.js` used in the other three projects is still exactly correct here.
<!-- In a real project's <head>, replace the CDN CSS <link> used in every earlier project with a link to your own compiled file: --><link href="custom.css" rel="stylesheet">
<!-- Bootstrap's JS bundle is completely unaffected by any of this - Sass only ever touches CSS, so the CDN <script> tag stays exactly as it was before --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>Complete Code
Here is the complete `custom.scss` from Steps 2 and 3, followed by the full demo page from Steps 5 and 6, linking the compiled `custom.css` this Sass file produces.
// custom.scss
$primary: #7c3aed;$secondary: #f59e0b;$body-bg: #faf5ff;$font-family-base: "Poppins", system-ui, sans-serif;$border-radius: 0.75rem;$border-radius-lg: 1rem;$enable-shadows: true;
@import "bootstrap/scss/bootstrap";
.text-gradient { background: linear-gradient(90deg, $primary, $secondary); -webkit-background-clip: text; background-clip: text; color: transparent;}<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lumen Studio</title> <!-- The compiled output of "npx sass custom.scss:custom.css" - this is what makes the whole page render with the Step 2/3 overrides applied --> <link href="custom.css" rel="stylesheet"></head><body>
<nav class="navbar navbar-expand-lg bg-white shadow-sm"> <div class="container"> <a class="navbar-brand fw-bold text-gradient" href="#">Lumen Studio</a> <div class="ms-auto"> <button class="btn btn-primary">Get Started</button> </div> </div> </nav>
<div class="container py-5"> <div class="row g-4"> <div class="col-md-4"> <div class="card h-100"> <div class="card-body"> <h3 class="h5 card-title">Design Tokens</h3> <p class="card-text">One set of Sass variables drives every component's color and shape.</p> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> <div class="col-md-4"> <div class="card h-100"> <div class="card-body"> <h3 class="h5 card-title">One Source of Truth</h3> <p class="card-text">Change a color once in custom.scss, not across dozens of hardcoded hex values.</p> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> <div class="col-md-4"> <div class="card h-100"> <div class="card-body"> <h3 class="h5 card-title">Still Just Bootstrap</h3> <p class="card-text">Every class here is stock Bootstrap - only the compiled variables underneath changed.</p> <button class="btn btn-secondary">Learn More</button> </div> </div> </div> </div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script></body></html>Sass cannot compile inside a browser tab with no build step, so the interactive preview below cannot run "npx sass custom.scss:custom.css" the way a real project's build pipeline would. Instead, the preview loads Bootstrap's normal CDN CSS and then layers a small hand-written <style> block on top that reproduces the same visual result the Step 2/3 overrides would produce once actually compiled - it approximates the compiled custom.css, it does not replace the real Sass build step a real project still needs.
Sample Run
Click Run to see what this code prints.
Extend This Project
- Override the `$theme-colors` Sass map to add a brand-new named color (e.g. `"brand"`) that automatically gets its own `.btn-brand`, `.text-brand`, and `.bg-brand` classes, the same way `primary`/`secondary` do.
- Turn off Bootstrap components you never use with `$enable-*` flags (e.g. `$enable-cssgrid: false`) before the import, shrinking the final compiled `custom.css` file size.
- Wire the Sass compile step into a bundler (Vite or webpack) so `custom.css` regenerates automatically as part of `npm run build`, instead of running the Sass CLI by hand.
- Layer Bootstrap 5.3's `[data-bs-theme="dark"]` attribute on top of this same custom.scss to produce a dark variant of the brand theme, without writing a second full override file.
- Extract `$primary`/`$secondary` into CSS custom properties (`--bs-primary`) so the brand colors can also be tweaked live with JavaScript after the page loads, not only at Sass compile time.
Summary
You customized Bootstrap at its source instead of fighting it with overriding CSS after the fact: setting `$primary`, `$secondary`, `$body-bg`, `$font-family-base`, and `$border-radius` before Bootstrap's own `@import`, adding one small Sass rule that reused those same brand variables, and compiling the result into a real `custom.css` that a production `<head>` links to in place of the CDN file. That override-before-import pattern is how every real, professionally themed Bootstrap site differs from a generic CDN demo.