Logo
Logo

Programming by Stealth

A blog and podcast series by Bart Busschots & Allison Sheridan.

PBS 183 of X: Customising Bootstrap with CSS 'Variables' (CSS)

25 Apr 2026

In the previous instalment, we learned how CSS provides Custom Properties to provide variable-like capabilities, and we learned how to use these ‘variables’ in our own CSS style sheets. Armed with this understanding, we can now explore the CSS variables Bootstrap provides, and how we can manipulate them to customise our Bootstrap-based websites and web apps.

Matching Podcast Episode

You can also Download the MP3

Read an unedited, auto-generated transcript with chapter marks: PBS_2026_04_25

Instalment Resources

PBS 182 Challenge Solution

The challenge set at the end of the previous instalment was to use CSS custom properties to style a little working web app. The challenge was to make three aspects of the calendar app’s look and feel customisable:

  1. The brand
  2. The readability
  3. The usability

The starting point provided contained a fully working calculator widget entirely contained within an HTML table. The needed styles to make the widget work were included, along with a placeholder for where the enhancements should be added. The widget was fully functional, with the jQuery-based JavaScript code provided. Neither the HTML nor the JavaScript was to be edited as the challenge was purely CSS-based.

You’ll find my full solution in the file pbs182-challenge-solution.html in the instalment ZIP. Opening the file in your browser will show, unsurprisingly, that I opted to brand my calculator using the font and accent colour from the PBS website 🙂

I also chose to use Google Fonts for my branding and UI, specifically:

This means I used the optional placeholder at the very top of the style sheet to import those fonts with an @import at-rule.

In terms of colours, I made the following decisions:

  1. I chose the title colour from the PBS website as my branding colour (#00408d)
  2. To use the chosen branding colour for the widget title and the widget border, and a calculated very light shade of that colour for the widget background
  3. For internal borders, all of which represent physical edges on a desktop calculator, I chose to hard-code them all to black, solid, and a single pixel.
  4. For the display I chose to work with an off-black background and an off-white for the text to simulate an LCD screen which never has a fully black background or fully white text. I chose to mix the brand colour with black to make the background, and with white to make the text colour.
  5. To avoid the ordinary buttons clashing with the brand colour I decided to hard-code them to neutral colours (equal parts red, green, and blue) — a light neutral grey for the background, and an extremely dark neutral grey for the text. As well as avoid colour clashes with all possible brand colours, it also ensured they’d always be easy to read.
  6. For the action buttons I chose a different approach — I wanted the deletion buttons to be a shade or red, and the calculate button a shade of green, but I wanted the brightness and saturation to always match the brand colour. That took some figuring out, but with a little help from Lumo and Kagi I found there is a CSS function that allows specific aspects of an existing colour to be combined with new values to create related colours.

The function I found for calculating related colours is oklch(), which, it turns out, is the name for the cylindrical version of the OKLab colour space (you can read more on Wikipedia). That means it uses Luminance (lightness), Chroma (saturation), and Hue (colour) coordinates. By keeping two of the three coordinates the same and varying another, you can get different shades, depths, and hues that are related to the original colour. In this case, I wanted to keep the luminance and chroma from the brand, but swing the hue to red for the deletion buttons and green for the calculate button. Based on the function’s documentation on the Mozilla Developer Network, I was able to get what I needed with:

/* special styles for the backspace and clear buttons */
table#calculator button[data-action="backspace"],
table#calculator button[data-action="clear"] {
	background-color: oklch(from var(--calc-brand-color) l c 40);
  color: white;
}

/** special styles for the calculate button */
table#calculator button[data-action="calculate"] {
  background-color: oklch(from var(--calc-brand-color) l c 160);
  color: white;
}

Simply by updating the button and display colouring to make them look like what they do, I gave my calculator a big usability boost, but there was one thing that still felt off — the operator buttons still looked the same as the digit buttons. This was easy to fix — I made the operator button text pure black, just a little darker than the very dark grey on the digit buttons, and bolded the font. With that change made, the action buttons looked less important than I felt they needed to, so for consistency, I bolded those too.

At that stage I’d handled two of the three requires — the only remaining area to address was readability. That basically boils down to sizing and spacing.

Since this is a user interface rather than a text document, I chose to work with pixels as my base unit rather than ems or rems (root EMs). I created a variable to hold a base font size, and then divided that by 8 to make an atomic spacing size:

/* the base font size for the calculator */
@property --calc-base-font-size-px { /* Be sure to keep divisible by 8 */
  syntax: '<integer>';
  inherits: true;
  initial-value: 24; /* 1.5x browser default */
}

/* the base spacing unit for the calculator — derive from the base font size */
@property --calc-space-px {
  syntax: '<integer>'; 
  inherits: true;
  /* no initial value — will compute from --calc-base-font-size-px */
}

/* … */

table#calculator {
  --calc-space-px: calc(var(--calc-base-font-size-px) / 8); /* set the base spacing unit to one eighth of the base font size */
  /* … */
}

In terms of font sizing I made the following decisions:

  1. Use the base font size for the widget title
  2. Use 2x the base font size for the display
  3. Use 1.5x the base font size for the buttons

Finally, I used the derived spacer for all paddings, margins, border radii, and even to set the widget’s overall width to 16 times the font size.

Introducing Bootstrap 5’s CSS ‘Variables’

As we learned in the previous instalment, CSS’s variable-like feature is formally known as custom properties, but developers widely refer to them as CSS Variables, and so does the Bootstrap documentation. So, to avoid confusion, we will adopt that term for this instalment.

Before we explain how they work, it’s important to emphasise that CSS variables are not Bootstrap’s primary mechanism for customisation — that remains the SASS CSS-preprocessor, even in Bootstrap 5. However, as Bootstrap matures, it is exposing more CSS variables, making it possible to do more customisations with pure CSS (i.e., without the extra complexity of a preprocessor). Still, it remains a limited feature, providing only a subset of what’s possible with SASS, and doing so in a less elegant way.

Having said that, being able to do any customisation without the need for a SASS compiler is a big improvement over early versions of Bootstrap, where you could do no customisation without SASS! So, I think this is a skill well worth learning. Besides, if the current trend continues, pure-CSS customisations are only going to become more powerful in future Bootstrap versions!

Bootstrap’s variables fall into two very distinct categories — global variables that are applied at the :root scope, and component-specific variables applied to the CSS classes for those components. Remember, in Bootstrap, components are things like Bootstrap alerts, Bootstrap navbars, Bootstrap button groups, etc..

Bootstrap’s documentation is organised in the same way as its underlying code, so it’s easy to pick up the scoping pattern for the variables:

As a general guideline, when you’re using Bootstrap to style content-oriented sites, you’ll be focusing mostly, if not completely, on the global variables, and when you’re styling web apps, you’ll be focusing on the component-specific variables. Since we have worn both of those hats throughout this series, we’ll explore both of these use cases in our examples.

Regardless of their scope, all Bootstrap’s CSS variables share the following:

  1. For your customisations to take effect, you must include Boostrap’s CSS before you Override any Bootstrap Variables. Your declarations need to override the originals, so they must come later in the document, otherwise your customisations will be overridden when Bootstrap gets imported!
  2. All the Bootstrap CSS variables are pre-fixed with bs-, making them easy to recognised in the browser’s developer tools (and showing how out-of-touch the developers are with modern slang! 😉).
  3. The official documentation leaves a lot to be desired when it comes to CSS variables 🙁. It’s clear SASS gets a lot more of the developer’s attention. In my experience, in 2026 at least, the browser’s developer tools are a better source of information than the docs!

Also note that the Bootstrap variables serve a dual purpose :

  1. They provide access to fine-grained parts of the Bootstrap style within your own CSS code — allowing you to seamlessly blend your styles into the Bootstrap styles
  2. They allow some aspects of Bootstrap’s appearance to be customised.

The vast majority of the variables serve both roles, with one notable exception — the break-point variables are provided purely for consumption, and editing them has no effect on a page’s behaviour. These breakpoint variables are not even listed in the documentation, but you can see them in the browser’s developer tools:

:root {
  --bs-breakpoint-lg: 992px;
  --bs-breakpoint-md: 768px;
  --bs-breakpoint-sm: 576px;
  --bs-breakpoint-xl: 1200px;
  --bs-breakpoint-xs: 0;
  --bs-breakpoint-xxl: 1400px;
}

Worked Example 1 — Customising a Bootstrap Web Page

We’re going to start with the globally scoped variables because they’re the simplest to understand, and by definition, have the most wide-ranging effect. Given that we know these are the most relevant to content-oriented sites, we’ll use a traditional web page rather than a web app for this first worked example.

Conveniently, these also happen to be the least poorly documented! You can get an almost full list of these variables (missing the aforementioned breakpoint variables), along with their default values, from the CSS Variables page in the documentation.

These variables are all defined at the :root scope, so you need make your customisations at the :root scope too!

Since these variables are concerned with the overall appearance of a webpage, they broadly fall into the following categories:

  1. Typography, e.g. --bs-body-font-family
  2. Colours, e.g. --bs-primary
  3. Borders, e.g. --bs-border-style

As an example, let’s use Bootstrap CSS variables in conjunction with our own style declarations to rebuild the quotation page example in the previous instalment using Bootstrap. We’ll tweak it to match the PBS house style.

You’ll find the source code before we make any Bootstrap customisations in the file pbs183-example1-webPage-1-defaultBootstrap.html in the instalment ZIP:

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>PBS 183 — Example 1 (Web Page - Default Bootstrap)</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
      <div class="row mt-3">
        <header class="col">
          <h1>PBS 183 — Example 1 (Web Page — Default Bootstrap)</h1>
        </header>
      </div>
      <div class="row">
        <main class="col">
          <p class="lead">True wisdom is a rare and wonderful thing, and it should be cherished where ever it's found.</p>

          <figure class="p-3 bg-info-subtle border-start border-info-subtle border-5 rounded-end-4">
            <blockquote class="blockquote">
              <p>"Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less."</p>
            </blockquote>
            <figcaption class="blockquote-footer">
              Marie Curie in a <cite title="1915 Letter to Irène Curie">1915 letter to her daughter Irène</cite>
            </figcaption>
          </figure>
        </main>
      </div>
    </div>
  </body>
</html>

As you can see, this is a very simple page! It uses the standard Bootstrap layout and content classes, so if you open it in your favourite browser, you’ll see it looks like a well-designed page. It also looks just like every other pure Bootstrap page, so it has no personality!

Notice that there’s not a single line of CSS code in this initial file — the styling is all coming from the standard Bootstrap CSS classes.

Let’s customise the design using both regular CSS and Bootstrap’s CSS variables.

You’ll find the fully customised version of the code in the file pbs183-example1-webPage-2-customBootstrap.html in the instalment ZIP.

If you open the file in your favourite browser you’ll see the at the following has been customised:

  1. The typography matches the PBS website — Ubuntu Mono for the headings, and Ubuntu Sans for the body text.
  2. The typography has also been expanded beyond both standard Bootstrap and the fonts used on the PBS website to add an additional cursive font for the quotation text — Indie Flower.
  3. The heading colour has been updated to match the PBS website.
  4. The info colours, used to style the quotation, have been customised to use shades of the PBS heading colour.

Example 1 Step 1 — Encode the Needed Fonts and Colours in Our Own Custom Properties

Rather than hard-coding values directly into the Bootstrap variables, it’s much better to define your own custom properties first, and then reference those when overriding Bootstrap. There are three reasons to do this:

  1. It makes debugging easier since all your magic values are grouped together in the property definitions.
  2. It avoids duplication, because for any realistic project, the same custom values will need to be assigned to multiple Bootstrap CSS variables, potentially in multiple contexts.
  3. Assuming you choose meaningful names, it makes the code more readable — a real boon for future you!

The first thing we need to do is add a new <style> tag to the <head> where we can add our customisations. This tag must be added after the <link rel="stylesheet"> used to import Bootstrap’s CSS!

Once we add the style tag we need to do a little more house-keeping before we can start our customisations — we need to import our custom fonts from Google Fonts at the very top of the style sheet:

/*
 * Import Desired Google Fonts:
 * - Indie Flower (a handwriting font)
 * - Ubuntu Mono (a monospaced font)
 * - Ubuntu Sans (a sans-serif font)
 */
@import url('https://fonts.googleapis.com/css2?family=Indie+Flower&family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Ubuntu+Sans:ital,wght@0,100..800;1,100..800&display=swap');

Before we start actually changing the look of our page, let's lay a solid foundation by encoding all the values we’ll need in some well named custom properties of our own:

/*
 * Delcare custom properties for the page's look and feel
 */

/* Custom properties for for the PBS typography */
@property --pbs-heading-font-family {
  syntax: "<string>#"; /* A comma-separated list of font names */
  inherits: true;
  initial-value: "Ubuntu Mono", "monospace";
}
@property --pbs-body-font-family {
  syntax: "<string>#";
  inherits: true;
  initial-value: "Ubuntu Sans", "sans-serif";
}
@property --pbs-handwriting-font-family {
  syntax: "<string>#";
  inherits: true;
  initial-value: "Indie Flower", "cursive";
}

/* Custom properties for the PBS colour scheme */
@property --pbs-logo-color {
  syntax: "<color>";
  inherits: true;
  initial-value: #00408d;
}

The approach here is completely in keeping with our examples from the previous instalment.

Example 1 Step 2 — Override the Appropriate Bootstrap Global CSS Variables

We can now start to apply our personality to the site by redefining all the relevant global Bootstrap CSS variables in the :root scope. Because we’re only using a very small subset of Bootstrap’s features in this simple page, there are just three relevant variables to update:

/*
 * Override default Bootstrap properties for the page
 */
:root {
  /* replace the default body font family with the PBS body font*/
  --bs-body-font-family: var(--pbs-body-font-family);

  /* replace the subtle info colours with variants of the PBS logo color */
  --bs-info-bg-subtle: color-mix(in srgb, var(--pbs-logo-color), white 95%);
  --bs-info-border-subtle: color-mix(in srgb, var(--pbs-logo-color), white 60%);
}

As you can see, we’ve set the body font to the PBS body font, and we’ve updated the variables that control the colours that will be applied by the bg-info-subtle & border-info-subtle CSS classes applied to the <figure> containing the quotation.

This is the HTML code in question:

<figure class="p-3 bg-info-subtle border-start border-info-subtle border-5 rounded-end-4">
  <blockquote class="blockquote">
    <p>"Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less."</p>
  </blockquote>
  <figcaption class="blockquote-footer">
    Marie Curie in a <cite title="1915 Letter to Irène Curie">1915 letter to her daughter Irène</cite>
  </figcaption>
</figure>

Notice that the CSS classes are bg-info-subtle and border-info-subtle, but the CSS variables are --bs-info-bg-subtle and --bs-info-border-subtle — the words are ordered differently 🤯. This is not a typo or a weird mistake; CSS is consistent in how it orders words in class names and how it orders words in variable names, the two categories just use different standards that are not consistent with each other. No idea why, but my strong advice is to copy and paste variable names whenever you can!

Given that the names are not the same, and the documentation is close to non-existent, how did I know which variable names matched the CSS class names?

I could have made an educated guess based on the list in the docs, but what I actually did was reach for my browser’s developer tools! In Safari’s Developer tools pane I select the <figure> with the Inspector, then scroll down to the relevant CSS properties in the Computed tag, which showed that the active rule for the background-color CSS property was var(--bs-info-bg-subtle)!important — this definitively showed me the specific variable that was actually in use by the browser. No ambiguity, no guesswork. I strongly recommend you use your browser’s developer tools to determine the relevant Bootstrap variable names!

Example 1 Step 3 — Apply Some Final Polish with Plain CSS

At this stage our page is starting to look more like a PBS page than a Bootstrap page, but it could do with a little more polish.

We’ve now done what we can via the Bootstrap CSS variables, so we need to use plain old CSS variables for the rest:

/*
 * Apply the PBS style to the page
 */
     
/* set the heading font on the tags, and the matching Bootstrap classes */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: var(--pbs-heading-font-family);
  color: var(--pbs-logo-color);
}

/* add some personality to the quotation */
.blockquote {
  font-family: var(--pbs-handwriting-font-family);
  color: var(--pbs-logo-color);
}

I just want to draw your attention to two things:

  1. These style declarations use our custom css properties rather than hard-coded values
  2. I chose to apply the custom font and colour to both the <h1><h6> tags, and the .h1.h6 Bootstrap utility classes. I did this to illustrate how to make your customisations Bootstrap-friendly. In this instance, our site did not uses Bootstrap’s .h1 class to style something that’s not an <h1> tag, but that’s a completely normal thing to do on a Bootstrap page, so your customisations should always bear that in mind!

Example 1 — Final Thoughts

If you weren’t following along making the edits yourself, you’ll find the final customised page with all the edits in the file pbs183-example1-webPage-customBootstrap.html in the instalment ZIP.

If you open this final version of the page in your favourite browser you’ll see that despite only writing a few tens of lines of code, we’ve really change the page’s personality. That’s why Bootstrap is so powerful — to achieve the same final page from scratch would have taken hundreds of lines of code! Because we built on top of Bootstrap, we were able to get to where we wanted to be with a smaller, simpler codebase, and we got there a lot more quickly!

Worked Example 2 — Customising a Bootstrap Web App

The first example gave a nice feel for how the globally-scoped Bootstrap CSS variables work, so let’s focus on the component-scoped variables in our second example. Since these are most relevant to web apps, let’s build a Bootstrap version of the calculator app from the challenge.

I recreated the same basic widget using Bootstrap CSS classes, which you’ll find in the instalment ZIP as pbs183-example2-webApp-1-defaultBootstrap.html. If you open the file in your favourite browser, you’ll see it’s a distinctly Bootstrap-looking version of our pocket calculator widget.

The source code is too long to include here, so open the file in your favourite code editor.

I’d like to draw your attention to the following important details:

  1. There is zero CSS code in this file, every aspect of the calculator is styled using the standard Bootstrap style classes. (The completely standard Bootstrap CSS is being applied of course, but that code is not in the file.)
  2. The widget is implemented as a Bootstrap Card component (.card).
  3. The widget title is implemented as a Card header.
  4. The body of the calculator is implemented using a Bootstrap grid (.container) inside a Card body. There is one grid row (.row) for each row of the UI. The row for the display has a single column (<.col>), but the majority of the button rows have four columns - the exception being the last row which has two .col-3 regular width columns, and one .col-6 double-width column for the calculate button.
  5. The display is a read-only <input> marked up as a Bootstrap Form Control with the optional .form-control-lg sizing class applied to make it bigger.
  6. The buttons are Bootstrap Button components (.btn) with the optional .btn-sm sizing class applied to make them smaller, and the w-100 utility class applied to stretch them to the full width of their containing .col.
    1. The digits are secondary buttons (.btn-secondary)
    2. The operators are primary buttons (.btn-primary)
    3. The backspace and clear actions are danger buttons (.btn-danger)
    4. The calculate button is a success button (.btn-success)

You might notice I changed the title from PBS Deluxe Calculator in the previous instalment to Bartificer Deluxe Calculator in this example. The reason is that I have a much better developed style guide for Bartificer Creations than I do for PBS! Most relevantly, the Style guide I commissioned the Icon Factory to develope for Bartificer creations includes a colour pallet designed to be compatible with Bootstrap!

Let’s now customise Bootstrap to style the calculator according to the Bartificer style guide.

Again, we need to lay a basic foundation before we can start our customisations. Just like before we need to add a <style> tag to the <head>, it’s vital we add it after the <link rel="stylesheet"> that imports Bootstrap's CSS.

Again, we need to load the needed custom fonts from Google Fonts at the very start of the style sheet. In this case that’s:

Example 2 Step 0 — Force a Universal Width (with a Read-Only Bootstrap CSS Variable)

While designing this example, I was struggling with two problems — I needed to keep the example simple, and yet, I wanted to illustrate the use of a Bootstrap CSS variable as an input to our own styles rather than as a value to change. Designing a good-looking widget that works at one breakpoint is work enough, but adding the needed styles and customisations to make it work equally well at all breakpoints would add a lot of complexity, distracting from important details.

I solved both problems at once by hard-coding the width of the widget to 80% the width of the sm Bootstrap breakpoint (and centering it by adding the Bootstrap class mx-auto).

To apply that desired width I used the the read-only Bootstrap variable --bs-breakpoint-sm variable with the CSS calc() function:

/* 
 * Basic layout
 */
     
/* set the width of the calculator widget so it fits nicely on even the samllest breakpoint*/
div#calculator {
  width: calc(var(--bs-breakpoint-sm) * 0.8); /* 80% of Bootrap's sm breakpoint */
}

Example 2 Step 1 — Encode the Needed Fonts and Colours in Custom Properties

With the foundations laid, we’ll start the meat of our customisation the same way we did previously, by capturing the Bartificer style information for the typography and colours in a suite of custom properties:

/*
 * Define custom properties for the Bartificer typography
 */
@property --bartificer-logo-font-family {
  syntax: '<string>#';
  inherits: true;
  initial-value: "Exo 2", "sans-serif";
}
@property --bartificer-body-font-family {
  syntax: '<string>#';
  inherits: true;
  initial-value: "Georama", "sans-serif";
}
@property --bartificer-monospace-font-family {
  syntax: '<string>#';
  inherits: true;
  initial-value: "Victor Mono", "monospace";
}
@property --bartificer-monospace-font-weight {
  syntax: '<integer>';
  inherits: true;
  initial-value: 500;
}

/*
 * Define custom properites for the Bartificer colour scheme
 */
@property --bartificer-logo-green {
  syntax: '<color>';
  inherits: true;
  initial-value: #93c01f;
}
@property --bartificer-bg {
  syntax: '<color>';
  inherits: true;
  initial-value: #faf8f0;
}
@property --bartificer-primary {
  syntax: '<color>';
  inherits: true;
  initial-value: #1da6e5;
}
@property --bartificer-secondary {
  syntax: '<color>';
  inherits: true;
  initial-value: #8c9daf;
}
@property --bartificer-success {
  syntax: '<color>';
  inherits: true;
  initial-value: #78a046;
}
@property --bartificer-danger {
  syntax: '<color>';
  inherits: true;
  initial-value: #dc5a3c;
}

Again, the structure is very similar to previously, but because we need more colours, we need more custom properties!

You might notice the font properties are a little different this time — their names are finer-grained (having names ending in both -font-family and -font-weight). This is because the Bartificer Creations style guide specifies different font weights for different fonts. One of those specifications in particular is noteworthy because it calls for the Medium variant of the font. CSS’s human-friendly support for font weights is sorely lacking; only two of the nine named weights have CSS special values — bold & regular. All other weights, including Medium, need to be assigned numerically. For Medium, that means font-weight: 500.

Example 2 Step 2 — Customise the Widget’s Card with the Bartificer Branding

Before we work on the calculator widget’s UI, let’s get the widget’s overall style in place.

Similar to how we made our changes in the first example, we’ll do this in two phases — first we override Bootstrap variables to get as much of our customisation as we can without defining our own styles, then we’ll add the final polish with just a few of our own styles.

In this case we’re working with a card component, so the relevant Bootstrap CSS variables are scoped to .card, not :root. For cards, the documentation does list the available variables … sort of.

What the documentation actually lists is the SASS source code that generates the CSS variables, but if you know the default prefix is bs-, you can infer all the names from the left-sides of the :. For example, you can interpret the line --#{$prefix}card-border-color: #{$card-border-color}; to infer that there’s a CSS variable named --bs-card-border-color.

Looking at the list of available component variables we can see that we can fully control the card’s colours. Specifically we can control:

  1. The card’s border colour
  2. The text and background colours within card headers
  3. The background colour for the card’s body

Let’s assert that control:

/*
 * Override the default Bootstrap card styles for the calculator card
 */
div#calculator {
  --bs-card-border-color: var(--bartificer-logo-green); /* override the card border colour */
  --bs-card-cap-bg: var(--bartificer-logo-green); /* override the card header background colour */
  --bs-card-cap-color: white; /* override the card header text colour */
  --bs-card-bg: var(--bartificer-bg); /* override the card background colour */
}

What we can’t control with the available list of variables is the header typography, so we need to do that with plain old CSS (using our custom properties):

/*
 * Style the Calculator header
 */
div#calculator .card-header {
  font-family: var(--bartificer-logo-font-family);
  font-size: 1.5rem;
  font-weight: bold;
}

Example 2 Step 3 — Customise the Calculator UI with the Bartificer Colours & Typography

Finally, we need to align the colours and typography for the calculator UI itself with the Bartificer Creations style guide.

Let’s start at the top and work our way down, so first up is the calculator display.

This is implemented with the form-control class, which is not a component, so unsurprisingly, selecting the <input type="text"> tag with the inspector in Safari’s developer tools doesn’t show many available Bootstrap CSS variables. In fact, there are none that were relevant to our needs 🙁

Again, we have to fall back to plain old CSS:

/*
 * Style the calculator display
 */
div#calculator .display {
  font-family: var(--bartificer-monospace-font-family);
  font-weight: var(--bartificer-monospace-font-weight);
}

With the display customised, the next step is the buttons.

In this case we are dealing with Bootstrap components, Buttons to be precise, so we can expect there to be more relevant variables, and there are!

Similar to the .form-control documentation, the Button documentation gives us the SASS code that generates the CSS variables, rather than the CSS variables themselves. But again, that’s enough for us to deduce the relevant variable names.

Looking through the list of variable names we can see that we can use Bootstrap variables to control button colours. The catch is that it’s not a single variable, but three:

  1. --bs-btn-bg to set the button background colour
  2. --bs-btn-border-color to set the button border colour
  3. --bs-btn-hover-bg to set the colour the button turns when hovered over

Put a pin in the fact that we need three variables — we’ll return to that in our discussion of the limits of Bootstrap’s CSS variables when compared to Bootstrap’s SASS variables!

What might be a little counterintuitive is that we need to set these same three variables multiple times. Why? Because we need to colour all four of the button types we use in our code differently. Specifically, we need to set each of these three variables four times — once for .btn-primary, again for .btn-secondary, yet again for .btn-danger, and one last time for .btn-success:

/*
 * Override the default Bootstrap button colours for the buttons inside the calculator
 */
div#calculator .btn-primary {
  --bs-btn-bg: var(--bartificer-primary);
  --bs-btn-border-color: color-mix(in srgb, var(--bartificer-primary), black 20%);
  --bs-btn-hover-bg: color-mix(in srgb, var(--bartificer-primary), black 20%);
}
div#calculator .btn-secondary {
  --bs-btn-bg: var(--bartificer-secondary);
  --bs-btn-border-color: color-mix(in srgb, var(--bartificer-secondary), black 20%);
  --bs-btn-hover-bg: color-mix(in srgb, var(--bartificer-secondary), black 20%);
}
div#calculator .btn-success {
  --bs-btn-bg: var(--bartificer-success);
  --bs-btn-border-color: color-mix(in srgb, var(--bartificer-success), black 20%);
  --bs-btn-hover-bg: color-mix(in srgb, var(--bartificer-success), black 20%);
}
div#calculator .btn-danger {
  --bs-btn-bg: var(--bartificer-danger);
  --bs-btn-border-color: color-mix(in srgb, var(--bartificer-danger), black 20%);
  --bs-btn-hover-bg: color-mix(in srgb, var(--bartificer-danger), black 20%);
}

Again, there are no Bootstrap CSS variables allowing us to control the typography, we we need to fall back to plain old CSS:

/*
 * Style the calculator buttons
 */
div#calculator .btn {
  font-family: var(--bartificer-body-font-family);
}
div#calculator button.action {
  font-weight: bold;
}

Nothing much to note here — we set the font on all buttons at once by targeting the generic .btn class, them we add a little extra customisation to the action buttons by targeting our own custom .action CSS class.

And that’s it — those are all our customisations!

Worked Example 2 — Final Thoughts

You’ll find the final customised file in the instalment ZIP as pbs183-example2-webApp-2-customBootstrap.html. Open the file in your favourite browser to see just how much we’ve personalised our little web app.

Again, like with regular web pages, we get a lot of value from using Bootstrap, and we don’t have to do that much extra work to assert our own personality.

However, we are now starting to touch the limits of this approach — most noticeably, we had to set three colours rather than one for each of our four button types. This is because Bootstrap remains a SASS-first platform. Pure CSS customisation is now possible, but it’s a fall-back option for when SASS is not an option.

The Limits

As we’ve just seen, customising Bootstrap with pure CSS is useful, but limited.

Most importantly, there are far fewer proverbial levers to pull in CSS than in SASS. Even if Bootstrap’s CSS variable support was as mature as its SASS implementation, SASS would be inherently more powerful.

To give just two examples, SASS supports dictionaries and loops, making it possible to extend fundamental Bootstrap concepts like the number of breakpoints that exist. There’s a dictionary in the Bootstrap SASS code that defines the screen widths for each of sm, md, lg & xl, and that dictionary is looped over many times to create all the different Bootstrap CSS classes that use breakpoints, like col-sm-12col-xl-12. To add an even bigger breakpoint, you would simply add a new entry to the dictionary, say gr for grandé, and then all the needed CSS classes would be created for you by the various loops in the Bootstrap SASS code. Without doing anything more, you could just start using col-gr-3, etc., in your code!

But, compounding that fundamental difference in capability is the fact that Bootstrap’s CSS variables are much less mature than Bootstrap’s SASS utilities. There are many Bootstrap features for which there are few, or even no, CSS variables.

Finally, because SASS is fundamentally more powerful, whenever there’s a tradeoff needed between making something easier to configure in SASS or in CSS, the Bootstrap team will always choose to optimise for SASS!

A great example of this is the colour variables we saw in both of our worked examples. Because Bootstrap is SASS-first, the developers were forced to decide where the colour math would be performed — at compile-time in SASS, or in the browser with CSS functions like calc() & colour-mix(). Because SASS is weighted more highly by the developers, they chose to do the math using SASS functions at compile time.

That example SASS-first design decision directly impacted both of our worked examples!

In our first example we had to customise three CSS colour variables to make our page adopt a coherent customised info look:

/* replace the subtle info colours with variants of the PBS logo color */
--bs-info-bg-subtle: color-mix(in srgb, var(--pbs-logo-color), white 95%);
--bs-info-border-subtle: color-mix(in srgb, var(--pbs-logo-color), white 60%);

Had we wanted to use all the possible variations of info and not just info-subtle we would have needed to assign five variables:

--bs-info
--bs-info-rgb
--bs-info-text-emphasis
--bs-info-bg-subtle
--bs-info-border-subtle

But on the other hand, to customise all the info variations via SASS, we would have needed to set just one variable — $info. All the needed variations would have been calculated from that one value automatically.

This effect is compounded for web apps — even our limited example, which only uses a subset of the variants of a subset of the standard styles on a single component type, required 12 variable assignments (four times three)! Had we needed all the possible variations for our buttons, we would have needed ten for each style:

--bs-btn-color
--bs-btn-bg
--bs-btn-border-color
--bs-btn-hover-color
--bs-btn-hover-bg
--bs-btn-hover-border-color
--bs-btn-focus-shadow-rgb
--bs-btn-active-color
--bs-btn-active-bg
--bs-btn-active-border-color

There are eight standard styles, so just to have every possible button variant customised we would need 80 variable assignments!

Sure, we could use color-mix() to compute most of them, but we would have to do that! We would need to decide on the formula to use, and write the CSS code — it would not just happen automatically like it does for similar customisations in SASS.

Were we working in SASS, we would need to assign just eight colours, one for each standard style, and that would not just give us every possible button variant, but every possible variant of every component!

Simply put, customising Bootstrap in SASS is orders of magnitude quicker and easier, and you can do a lot more to boot!

Final Thoughts

In the previous instalment we learned how CSS custom properties work as CSS’s equivalent to traditional variables. We have now seen how Bootstrap allows us some useful but limited customisation options via these custom properties (which it calls CSS Variables). For simple websites and small web apps this is probably both practical and sufficient, but for any more advanced projects it’s simply not adequate.

To truly customise Bootstrap, you need to add SASS into your build process.

Jekyll, the static site generator used by default on GitHub Pages, has built-in support for SASS. That means that in the context of our main topic we don’t actually need to do any extra work to avail of the power of SASS!

So, before we shift our focus to back to Jekyll, we’re going to do two more things:

  1. Explore the basics of how SASS works
  2. Look at how Bootstrap can be customised with SASS

Join the Community

Find us in the PBS channel on the Podfeet Slack.

Podfeet Slack