Blog

  • How to Add Dark Mode to Any Bootstrap 5 HTML Template

    How to Add Dark Mode to Any Bootstrap 5 HTML Template

    How to Add Dark Mode to Any Bootstrap 5 HTML Template

    How to Add Dark Mode to Any Bootstrap 5 HTML Template

    Dark mode has moved from a niche preference to a genuine user expectation. In 2026, if your Bootstrap 5 HTML template does not support it, you are leaving a significant slice of your audience squinting at a blinding white screen — especially late-night users on OLED displays. The good news is that Bootstrap 5.3 introduced first-class dark mode support, and even if you are running an older Bootstrap 5 build, implementing a solid dark mode toggle requires surprisingly little code.

    In this guide you will learn exactly how to add dark mode to any Bootstrap 5 HTML template — from the native data-bs-theme attribute approach, through CSS custom properties, to a JavaScript toggle that persists user preference across sessions. Every approach includes real, copy-pasteable code. Whether you are customising a premium template like Canvas Template or building something from scratch, this walkthrough has you covered.

    Key Takeaways

    • Bootstrap 5.3+ includes a native data-bs-theme attribute that enables dark mode with zero extra CSS for core components.
    • CSS custom properties (variables) are the cleanest way to extend dark mode to your own custom components beyond Bootstrap’s defaults.
    • A JavaScript toggle that writes to localStorage ensures users do not have to re-select their preference on every page load.
    • The prefers-color-scheme media query lets you respect the OS-level preference automatically as a sensible default.
    • Accessibility is non-negotiable — always verify contrast ratios in both light and dark modes against WCAG 2.1 AA standards.
    • SCSS variable overrides give you granular control over dark mode colours when using a build pipeline.

    Understanding Bootstrap 5.3’s Dark Mode Architecture

    Before writing a single line of code it is worth understanding how Bootstrap 5.3 implements dark mode under the hood. The framework ships two colour mode styles — light and dark — controlled by a single HTML attribute: data-bs-theme. Apply it to the <html> element to switch the entire page, or scope it to any individual component.

    Internally, Bootstrap maps its component colours to a set of CSS custom properties (--bs-body-bg, --bs-body-color, --bs-card-bg, and so on). When data-bs-theme="dark" is present, Bootstrap redefines those custom properties with dark-appropriate values. Every component — cards, navbars, modals, dropdowns, badges — responds automatically.

    If you are on Bootstrap 5.0–5.2, this native mechanism does not exist, and you will need to rely entirely on a custom CSS class approach. We will cover both paths below.

    <!-- Bootstrap 5.3+: whole-page dark mode -->
    <html lang="en" data-bs-theme="dark">
    
    <!-- Bootstrap 5.3+: scoped dark mode on a single card -->
    <div class="card" data-bs-theme="dark">
      <div class="card-body">
        <h5 class="card-title">Dark Card</h5>
        <p class="card-text">Only this card is dark.</p>
      </div>
    </div>

    The scoped approach is particularly powerful when you want individual sections — a hero, a sidebar, a footer — to sit in dark mode while the rest of the page remains light. This is a pattern used extensively in Canvas Template’s multi-section layouts.

    Using CSS Custom Properties to Extend Dark Mode to Custom Components

    Bootstrap’s built-in dark mode covers its own components perfectly, but your template almost certainly has custom UI elements — hero sections, pricing tables, timeline blocks, testimonial cards — that Bootstrap knows nothing about. This is where you extend the system using the same CSS custom property pattern Bootstrap itself uses.

    The recommended pattern is to define your custom properties under :root for light mode and then override them inside a [data-bs-theme="dark"] selector block.

    /* light mode defaults — defined on :root */
    :root {
      --custom-hero-bg: #f8f9fa;
      --custom-hero-color: #212529;
      --custom-card-border: #dee2e6;
      --custom-highlight: #0d6efd;
      --custom-muted-bg: #e9ecef;
    }
    
    /* dark mode overrides */
    [data-bs-theme="dark"] {
      --custom-hero-bg: #0f1117;
      --custom-hero-color: #e8eaf0;
      --custom-card-border: #2c2f36;
      --custom-highlight: #6ea8fe;
      --custom-muted-bg: #1a1d24;
    }
    
    /* usage in component styles */
    .hero-section {
      background-color: var(--custom-hero-bg);
      color: var(--custom-hero-color);
    }
    
    .feature-card {
      border: 1px solid var(--custom-card-border);
      background-color: var(--custom-muted-bg);
    }

    Because these properties cascade, any component that consumes them will automatically flip when the theme attribute changes — no JavaScript required for the visual update, only for toggling the attribute itself.

    If your project uses SCSS, this integrates beautifully with Bootstrap’s variable system. Our guide on how to use SCSS variables to theme a Bootstrap 5 site goes deeper on the build-pipeline side of this, including how to override $body-bg, $body-color, and component-level SCSS maps before compilation.

    Building the JavaScript Toggle With localStorage Persistence

    A dark mode toggle is only genuinely useful if it remembers the user’s choice. Here is a complete, production-ready implementation that reads and writes to localStorage, respects the OS preference as a starting default, and updates a toggle button’s accessible label.

    <!-- Toggle button — place in your navbar -->
    <button
      id="theme-toggle"
      type="button"
      class="btn btn-sm btn-outline-secondary"
      aria-label="Switch to dark mode"
    >
      <svg id="icon-sun" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
        <path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13z"/>
      </svg>
      <svg id="icon-moon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="d-none">
        <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
      </svg>
    </button>
    // theme-toggle.js
    (function () {
      const htmlEl = document.documentElement;
      const toggleBtn = document.getElementById('theme-toggle');
      const iconSun = document.getElementById('icon-sun');
      const iconMoon = document.getElementById('icon-moon');
    
      // Determine initial theme:
      // 1. Check localStorage first
      // 2. Fall back to OS preference
      // 3. Default to light
      function getPreferredTheme() {
        const stored = localStorage.getItem('bs-theme');
        if (stored) return stored;
        return window.matchMedia('(prefers-color-scheme: dark)').matches
          ? 'dark'
          : 'light';
      }
    
      function applyTheme(theme) {
        htmlEl.setAttribute('data-bs-theme', theme);
        localStorage.setItem('bs-theme', theme);
    
        if (theme === 'dark') {
          iconSun.classList.remove('d-none');
          iconMoon.classList.add('d-none');
          toggleBtn.setAttribute('aria-label', 'Switch to light mode');
        } else {
          iconSun.classList.add('d-none');
          iconMoon.classList.remove('d-none');
          toggleBtn.setAttribute('aria-label', 'Switch to dark mode');
        }
      }
    
      // Apply on page load — run before DOMContentLoaded to avoid flash
      applyTheme(getPreferredTheme());
    
      toggleBtn.addEventListener('click', function () {
        const current = htmlEl.getAttribute('data-bs-theme');
        applyTheme(current === 'dark' ? 'light' : 'dark');
      });
    
      // Sync if OS preference changes while page is open
      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
        if (!localStorage.getItem('bs-theme')) {
          applyTheme(e.matches ? 'dark' : 'light');
        }
      });
    })();

    A critical detail: call applyTheme(getPreferredTheme()) as early as possible — ideally in a <script> tag placed immediately after the opening <body> tag, before any visible content renders. This eliminates the “flash of wrong theme” (FOWT) that plagues many dark mode implementations.

    Dark Mode for Bootstrap 5.0–5.2 (The Class-Based Approach)

    If you are using Bootstrap 5.0, 5.1, or 5.2, the data-bs-theme attribute is not available. The established approach here is a toggling CSS class — typically .dark-mode — on the <body> or <html> element, combined with a full set of overriding styles.

    /* styles for Bootstrap 5.0-5.2 dark mode */
    body.dark-mode {
      background-color: #0f1117;
      color: #e8eaf0;
    }
    
    body.dark-mode .navbar {
      background-color: #1a1d24 !important;
    }
    
    body.dark-mode .card {
      background-color: #1e2128;
      border-color: #2c2f36;
      color: #e8eaf0;
    }
    
    body.dark-mode .card-header,
    body.dark-mode .card-footer {
      background-color: #252830;
      border-color: #2c2f36;
    }
    
    body.dark-mode .form-control,
    body.dark-mode .form-select {
      background-color: #1a1d24;
      border-color: #3a3d46;
      color: #e8eaf0;
    }
    
    body.dark-mode .table {
      --bs-table-bg: #1e2128;
      --bs-table-striped-bg: #252830;
      --bs-table-hover-bg: #2a2d36;
      color: #e8eaf0;
      border-color: #2c2f36;
    }
    // JavaScript for Bootstrap 5.0-5.2
    const body = document.body;
    const toggleBtn = document.getElementById('theme-toggle');
    
    const saved = localStorage.getItem('dark-mode');
    if (saved === 'true' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
      body.classList.add('dark-mode');
    }
    
    toggleBtn.addEventListener('click', function () {
      body.classList.toggle('dark-mode');
      localStorage.setItem('dark-mode', body.classList.contains('dark-mode'));
    });

    This approach is more verbose but works reliably. The main downside compared to the 5.3 approach is that you need to maintain a comprehensive override list as you add new components. For large templates with many custom components, this can become significant CSS maintenance overhead.

    Comparing Dark Mode Approaches: Which Should You Use?

    Approach Bootstrap Version CSS Effort JS Effort Component Coverage Best For
    data-bs-theme attribute 5.3+ Low (only custom components) Low Excellent (automatic) New projects, Canvas Template users
    CSS custom properties extension 5.3+ Medium None extra Full (custom + Bootstrap) Extending any 5.3+ template
    Body class toggle 5.0–5.2 High (full overrides) Low Manual — whatever you write Legacy Bootstrap 5 projects
    Separate dark stylesheet Any Very High Medium Full control Large teams with design system
    SCSS-compiled dark theme Any Medium (SCSS maps) Low Full (compiled in) Build-pipeline projects

    For most developers working with a modern Bootstrap 5.3 template in 2026, the combination of data-bs-theme on the <html> element plus CSS custom properties for custom components is the clear winner — it is the least code for the most coverage.

    Accessibility and Contrast in Dark Mode

    Adding dark mode is not simply a visual swap — it is an accessibility feature in its own right, and it can just as easily introduce accessibility problems if not tested rigorously. WCAG 2.1 AA requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text, in both modes.

    Common dark mode contrast failures include:

    • Muted text colours (text-muted, .text-secondary) that already barely pass in light mode dropping below threshold in dark mode
    • Placeholder text in form inputs becoming invisible
    • Disabled state colours that blend into dark backgrounds
    • Icon colours not updating alongside text colours
    /* Fix common contrast failures in dark mode */
    [data-bs-theme="dark"] .text-muted {
      color: #9aa0ac !important; /* passes 4.6:1 on #0f1117 bg */
    }
    
    [data-bs-theme="dark"] ::placeholder {
      color: #6c757d;
      opacity: 1;
    }
    
    [data-bs-theme="dark"] .form-control:disabled {
      background-color: #2a2d36;
      color: #6c757d;
    }

    Always run both your light and dark mode versions through a contrast checker such as the WebAIM Contrast Checker or the browser’s built-in accessibility panel. Our detailed post on making a Bootstrap 5 website accessible to WCAG 2.1 AA covers systematic contrast auditing, focus indicators, and ARIA patterns that you should apply in parallel with your dark mode work.

    Also remember to update ARIA labels on your toggle button dynamically, as shown in the JavaScript snippet above. Screen reader users need to know what action the button will perform, not what mode is currently active — so “Switch to light mode” is the correct label when dark mode is on.

    Handling Images, SVGs, and Media in Dark Mode

    One area developers consistently overlook is imagery. A bright white logo or illustration can look jarring on a dark background, and certain photographs lose their intended mood when surrounded by dark chrome.

    Several practical techniques help here:

    /* Reduce brightness of photographs in dark mode */
    [data-bs-theme="dark"] img:not([data-no-dim]) {
      filter: brightness(0.85) contrast(1.05);
      transition: filter 0.3s ease;
    }
    
    /* Invert simple black-on-white SVG logos */
    [data-bs-theme="dark"] .logo-img {
      filter: invert(1) hue-rotate(180deg);
    }
    
    /* Use the CSS picture element trick for art-directed dark images */
    <!-- Art-directed dark mode image using <picture> -->
    <picture>
      <source
        srcset="/images/hero-dark.webp"
        media="(prefers-color-scheme: dark)"
      />
      <img src="/images/hero-light.webp" alt="Hero illustration" class="img-fluid" />
    </picture>

    The <picture> element approach is ideal for hero illustrations and brand assets where you have specifically designed dark variants. For general photography, the filter: brightness() approach is a decent automatic solution, though it should be applied selectively rather than globally.

    For SVG icons inline in the DOM, they naturally inherit currentColor if built correctly, which means they will respond to your dark mode text colour changes without any extra work. This is one of many reasons to prefer inline SVG over icon fonts or <img>-embedded SVGs where possible.

    Performance Considerations and Dark Mode Best Practices

    Dark mode, if implemented carelessly, can introduce layout shift, flash of wrong theme, or unnecessary render-blocking. Here are the practices that keep things fast and smooth.

    Avoid FOWT with an inline script: Place the theme-detection script in a <script> tag in the <head> — not deferred, not async. Yes, this is a rare case where a synchronous script in the head is the correct decision. It reads from localStorage and sets the attribute before the browser paints anything.

    <head>
      <meta charset="UTF-8" />
      <!-- Inline FOWT prevention script — keep synchronous -->
      <script>
        (function() {
          var theme = localStorage.getItem('bs-theme') ||
            (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
          document.documentElement.setAttribute('data-bs-theme', theme);
        })();
      </script>
      <link rel="stylesheet" href="bootstrap.min.css" />
      <link rel="stylesheet" href="style.css" />
    </head>

    Use CSS transitions sparingly: Adding a global transition: background-color 0.3s, color 0.3s to the body sounds nice but it triggers repaints on every property of every element simultaneously. Instead, apply transitions only to elements where the animation adds genuine value — the toggle button icon, a hero section background, navigation bars.

    Lazy-load dark-mode-specific assets: If your dark mode uses entirely different hero images, consider loading them dynamically via JavaScript only when dark mode is active rather than bundling both into every page load. This ties into the broader performance conversation — our post on page speed optimisation for Bootstrap 5 HTML templates covers asset loading strategies in detail.

    Test in both modes during development: It sounds obvious but many bugs only appear in dark mode — box shadows that disappear, borders that vanish into backgrounds, focus rings that become invisible. Add dark mode testing to your standard browser QA checklist.

    If you want to skip manual implementation entirely and work in a visual builder environment, CanvasBuilder — the AI website builder built on top of Canvas Template — handles dark mode configuration through a simple UI toggle, generating all the correct attributes and custom property overrides for you. It is a great option if you want the flexibility of Bootstrap 5 without hand-coding every theme variable.

    For a deeper look at theming patterns in Canvas Template specifically, the post on Bootstrap 5 card component variants shows how card elements respond to theme attributes, which is helpful when you are building content-heavy dark mode layouts.


    Frequently Asked Questions

    Does Bootstrap 5 support dark mode natively?

    Yes, but only from version 5.3 onwards. Bootstrap 5.3 introduced the data-bs-theme attribute which switches all built-in components between light and dark colour palettes using CSS custom properties. If you are on Bootstrap 5.0–5.2, you need to implement dark mode manually via a toggling CSS class and override styles.

    How do I prevent the flash of wrong theme (FOWT) in Bootstrap 5?

    Place a small inline (synchronous) <script> tag in the <head> element — before your stylesheets if possible, but at minimum before any visible content. This script reads from localStorage and sets data-bs-theme on the <html> element immediately. Because it runs synchronously, the browser sets the attribute before it renders any content, eliminating the flash.

    Can I apply dark mode to only part of a Bootstrap 5 page?

    Yes. The data-bs-theme attribute can be applied to any HTML element, not just the root element. You could have a dark navigation bar (<nav data-bs-theme="dark">), a light main content area, and a dark footer all on the same page. This scoped approach is one of the most powerful features of Bootstrap 5.3’s implementation.

    How do I make dark mode work with custom components that Bootstrap doesn’t know about?

    Use CSS custom properties. Define your custom component colours as CSS variables under :root for light mode, then redefine them inside a [data-bs-theme="dark"] selector block. Your component styles consume those variables, so they automatically update when the theme attribute changes. No additional JavaScript is needed for the visual update — only for toggling the attribute itself.

    Should I use prefers-color-scheme or a manual toggle for dark mode?

    Both — in combination. Use prefers-color-scheme as the sensible default when a user visits your site for the first time with no stored preference. Then give them a manual toggle to override it, persisting their choice in localStorage. Also listen for changes to the OS-level preference and update automatically if the user has not manually overridden it. This covers all scenarios and respects user autonomy.


    Ready to Build With Dark Mode From Day One?

    Stop retrofitting dark mode onto templates that were never designed for it. Canvas Template is a premium Bootstrap 5.3 HTML template built with CSS custom properties throughout, making dark mode implementation clean, fast, and maintainable. Every component is structured to respond to data-bs-theme out of the box.

    Prefer a visual approach? CanvasBuilder — the AI website builder — lets you configure dark mode, colour palettes, and component themes without touching a line of CSS.

  • Page Speed Optimisation for Bootstrap 5 HTML Templates

    Page Speed Optimisation for Bootstrap 5 HTML Templates

    Page Speed Optimisation for Bootstrap 5 HTML Templates

    Page Speed Optimisation for Bootstrap 5 HTML Templates

    You spent hours choosing the right Bootstrap 5 HTML template, customising the navbar, fine-tuning the colour palette, and wiring up the components. Then you run a Lighthouse audit and get a performance score of 54. That stings — and it happens more often than you’d think.

    Bootstrap 5 is a fantastic foundation. It ships with a well-structured grid, a lean JavaScript bundle, and tree-shakeable SCSS. But out of the box, most commercial HTML templates are optimised for feature completeness, not delivery speed. Images are uncompressed, full CSS bundles are loaded even when you’re only using 20% of the classes, and third-party scripts pile up without a loading strategy.

    This guide walks you through every meaningful optimisation layer — from CSS and JavaScript bundling to image formats, font loading, and server-level caching — with real code examples you can drop straight into your Bootstrap 5 project. Whether you’re working with a premium template like Canvas Template or building from scratch, these techniques will push your performance scores into the green.

    Key Takeaways

    • Purge unused Bootstrap CSS with PurgeCSS or a custom SCSS build to cut stylesheet size by 60–80%.
    • Defer non-critical JavaScript and use Bootstrap 5’s ESM bundle for tree-shaking individual components.
    • Serve images in WebP or AVIF format and always include width/height attributes to eliminate layout shift (CLS).
    • Use font-display: swap and preload only the font weights you actually need.
    • Enable HTTP/2, Brotli compression, and long-lived cache headers at the server or CDN layer.
    • Measure with Lighthouse, WebPageTest, and Chrome DevTools — not just one tool.
    • If you want speed with zero build-step friction, CanvasBuilder handles asset optimisation automatically.

    Why Bootstrap Templates Underperform Out of the Box

    Before we fix anything, it’s worth understanding the pattern. A typical premium Bootstrap 5 template ships with:

    • A full compiled bootstrap.min.css (~30 KB gzipped, but up to 190 KB raw)
    • Multiple plugin stylesheets: Swiper, Lightbox, AOS, GLightbox, etc.
    • A monolithic theme.js that imports every plugin regardless of page
    • Hero images at 3000 × 1800 px saved as PNG or high-quality JPG
    • Google Fonts loaded via a blocking <link> in the <head>

    None of this is reckless — template authors need to demonstrate all features and keep setup simple for buyers. But once you move from demo to production, your job is to strip the template down to exactly what your page needs and deliver it as efficiently as possible.

    This is also relevant if you’re comparing delivery models. Our post on HTML Template vs WordPress Theme: Which Should You Choose in 2026? highlights that one of the strongest performance arguments for HTML templates is precisely this level of control — no PHP overhead, no plugin bloat — but only if you exercise that control.

    Purge and Minify Your CSS

    Bootstrap 5’s full CSS file contains styles for every component — carousels, toasts, offcanvases, spinners — most of which you’ll never use on any given page. The fastest way to shrink your stylesheet is to purge unused classes before you ship.

    Option A: Custom SCSS Build (Recommended)

    If your template ships with SCSS source files (Canvas Template does), you can import only the partials you need:

    // custom-bootstrap.scss
    
    // Required
    @import "bootstrap/scss/functions";
    @import "bootstrap/scss/variables";
    @import "bootstrap/scss/maps";
    @import "bootstrap/scss/mixins";
    @import "bootstrap/scss/root";
    
    // Layout & grid
    @import "bootstrap/scss/grid";
    @import "bootstrap/scss/containers";
    @import "bootstrap/scss/utilities";
    
    // Components you actually use
    @import "bootstrap/scss/buttons";
    @import "bootstrap/scss/navbar";
    @import "bootstrap/scss/card";
    @import "bootstrap/scss/modal";
    @import "bootstrap/scss/alert";
    
    // Utilities API
    @import "bootstrap/scss/utilities/api";
    

    By commenting out unused component imports, you can trim the output to under 40 KB raw before minification — sometimes far less. Check out our guide on how to use SCSS variables to theme a Bootstrap 5 site for a deeper dive into structuring your build.

    Option B: PurgeCSS as a PostCSS Plugin

    If you’re working with compiled CSS, add PurgeCSS to your build pipeline:

    // postcss.config.js
    const purgecss = require("@fullhuman/postcss-purgecss");
    
    module.exports = {
      plugins: [
        purgecss({
          content: ["./**/*.html", "./src/**/*.js"],
          safelist: {
            standard: [/^modal/, /^show/, /^fade/, /^collapse/, /^collapsing/],
            deep: [/tooltip/, /popover/],
          },
          defaultExtractor: content =>
            content.match(/[\w-/:]+(?<!:)/g) || [],
        }),
        require("cssnano")({ preset: "default" }),
      ],
    };
    

    The safelist array is critical. Bootstrap adds and removes classes dynamically via JavaScript (modal states, collapse transitions), so you must whitelist any class that’s injected at runtime rather than present in your HTML source.

    Tree-Shake Bootstrap JavaScript

    Bootstrap 5 ships an ESM (ES Module) build specifically designed for tree-shaking. Instead of loading the entire bundle, import only the components you need:

    // main.js — import only what the page uses
    
    import { Modal } from "bootstrap/js/dist/modal";
    import { Collapse } from "bootstrap/js/dist/collapse";
    import { Tooltip } from "bootstrap/js/dist/tooltip";
    
    // Initialise tooltips
    document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
      new Tooltip(el);
    });
    

    Compared to loading bootstrap.bundle.min.js (which includes Popper.js at ~45 KB gzipped), a hand-picked ESM build for a page that only needs modals and collapse can weigh in under 12 KB gzipped.

    For non-Bootstrap scripts — analytics, chat widgets, cookie banners — always load them with the correct attribute:

    <!-- Defer execution until DOM is ready -->
    <script src="theme.js" defer></script>
    
    <!-- Async for independent third-party scripts -->
    <script src="https://cdn.analytics.io/tag.js" async></script>
    

    Never place third-party scripts in <head> without async or defer. A single render-blocking script can add hundreds of milliseconds to your Time to First Byte (TTFB) and First Contentful Paint (FCP).

    Image Optimisation for HTML Templates

    Images consistently account for 60–80% of page weight on typical marketing pages. This is where you find the biggest performance wins.

    Use Modern Formats: WebP and AVIF

    AVIF offers 50% better compression than JPEG at equivalent quality. WebP is 25–35% better than JPEG and has near-universal browser support. Use the <picture> element to serve the best format the browser supports:

    <picture>
      <source
        srcset="hero.avif"
        type="image/avif">
      <source
        srcset="hero.webp"
        type="image/webp">
      <img
        src="hero.jpg"
        alt="Agency hero image"
        width="1440"
        height="720"
        class="img-fluid w-100"
        loading="eager"
        fetchpriority="high">
    </picture>
    

    Always specify width and height attributes on <img> elements. This allows the browser to reserve space before the image loads, eliminating Cumulative Layout Shift (CLS) — one of the Core Web Vitals that most directly affects your Lighthouse score.

    Lazy Load Below-the-Fold Images

    <img
      src="team-member.webp"
      alt="Jane Smith, Creative Director"
      width="400"
      height="400"
      class="rounded-circle img-fluid"
      loading="lazy"
      decoding="async">
    

    Use loading="eager" and fetchpriority="high" for your Largest Contentful Paint (LCP) element — typically the hero image. Use loading="lazy" for everything below the fold. Never lazy-load above-the-fold images; it actively hurts LCP.

    Responsive Images with srcset

    <img
      src="feature-640.webp"
      srcset="feature-640.webp 640w,
              feature-1024.webp 1024w,
              feature-1440.webp 1440w"
      sizes="(max-width: 768px) 100vw,
             (max-width: 1200px) 50vw,
             33vw"
      alt="Feature screenshot"
      width="1440"
      height="810"
      loading="lazy"
      class="img-fluid">
    

    Font Loading Strategy

    Web fonts are one of the most common causes of both render-blocking and layout shift. The default Google Fonts embed pattern blocks rendering; the optimised approach does not.

    Self-Host and Preload

    <!-- In <head>: preload the most critical weight only -->
    <link rel="preload"
      href="/fonts/inter-var.woff2"
      as="font"
      type="font/woff2"
      crossorigin="anonymous">
    
    /* In your CSS */
    @font-face {
      font-family: "Inter";
      src: url("/fonts/inter-var.woff2") format("woff2");
      font-weight: 100 900;
      font-style: normal;
      font-display: swap; /* Show fallback text immediately */
    }
    

    font-display: swap ensures text is visible immediately using a system font fallback while your custom font loads. This eliminates invisible text flashes (FOIT) and dramatically improves perceived performance.

    If you must use Google Fonts, at minimum add display=swap to the URL and preconnect to the Google domains:

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
      rel="stylesheet">
    

    Load only the font weights you actually use. Every additional weight is an extra network request. If your design uses 400 and 700, don’t load 300, 500, 600, 800, and 900.

    Critical CSS and Render-Blocking Resources

    The browser can’t render anything until it has processed all CSS in <head>. For large stylesheets, this creates a visible delay before any content appears on screen.

    The solution is to inline the CSS required to render above-the-fold content (critical CSS) and load the full stylesheet asynchronously:

    <head>
      <!-- Inline critical CSS -->
      <style>
        /* Minimal styles for navbar, hero section, and LCP image */
        body { margin: 0; font-family: Inter, system-ui, sans-serif; }
        .navbar { display: flex; align-items: center; padding: 1rem 1.5rem; }
        .hero { min-height: 100vh; display: flex; align-items: center; }
        /* ... */
      </style>
    
      <!-- Load full stylesheet asynchronously -->
      <link rel="preload"
        href="/css/theme.min.css"
        as="style"
        onload="this.onload=null;this.rel='stylesheet'">
      <noscript>
        <link rel="stylesheet" href="/css/theme.min.css">
      </noscript>
    </head>
    

    Tools like criticalcss.com or the critical npm package can automate critical CSS extraction from your HTML pages.

    Server, CDN, and Caching Optimisation

    Application-level optimisations only go so far. Your server and delivery infrastructure have a significant impact on real-world performance, especially for global users.

    Compression: Brotli Over Gzip

    Brotli compression (used by all major browsers) achieves 15–20% better compression ratios than gzip for text assets. Enable it in your Nginx config:

    # nginx.conf
    brotli on;
    brotli_comp_level 6;
    brotli_types
      text/html
      text/css
      application/javascript
      application/json
      image/svg+xml
      font/woff2;
    

    Cache Headers for Static Assets

    # Cache fingerprinted assets for 1 year
    location ~* \.(css|js|woff2|webp|avif|png|jpg)$ {
      add_header Cache-Control "public, max-age=31536000, immutable";
    }
    
    # Cache HTML for 1 hour with revalidation
    location ~* \.html$ {
      add_header Cache-Control "public, max-age=3600, must-revalidate";
    }
    

    Ensure your build pipeline appends a content hash to CSS and JS filenames (theme.a3f9c1.min.css) so you can safely set long cache TTLs without worrying about stale assets after deployments.

    Performance Comparison: Optimised vs Unoptimised

    Metric Unoptimised Template After Optimisation Improvement
    Total CSS size (raw) ~190 KB ~32 KB 83% smaller
    Total JS size (gzipped) ~62 KB ~14 KB 77% smaller
    Hero image ~450 KB JPG ~58 KB AVIF 87% smaller
    Lighthouse Performance 52–61 91–97 +35–45 pts
    LCP 3.8s 1.2s 68% faster
    Total Blocking Time 620ms 80ms 87% reduction

    Measuring Performance the Right Way

    Your Lighthouse score in Chrome DevTools is a useful indicator but not the full picture. Here’s a recommended measurement stack:

    • Lighthouse (Chrome DevTools or CLI) — Best for identifying specific issues and tracking improvements during development. Always run in Incognito mode to avoid extension interference.
    • WebPageTest (webpagetest.org) — Test from real browser instances in multiple global locations. Essential for validating CDN performance and seeing actual waterfall charts.
    • PageSpeed Insights — Combines lab data (Lighthouse) with real-world CrUX field data for URLs that have enough traffic. The field data is more honest than lab scores.
    • Chrome DevTools Performance tab — For diagnosing specific JavaScript execution bottlenecks and long tasks that inflate Total Blocking Time.

    If you’re evaluating which template to start from, our post on the best free Bootstrap 5 templates for agencies in 2026 includes performance considerations alongside design quality.

    One thing worth noting: performance isn’t isolated from other quality concerns. Accessibility matters too — poorly structured markup, missing alt attributes, and improperly loaded assets simultaneously hurt your accessibility score and your performance score. We covered the accessibility side in depth in How to Make a Bootstrap 5 Website Accessible (WCAG 2.1 AA).

    For teams who want the performance wins without managing a complex build pipeline manually, CanvasBuilder — the AI website builder built on the Canvas Template design system — handles image optimisation, CSS purging, and asset bundling automatically. It’s worth evaluating if your project timeline is tight.


    Frequently Asked Questions

    Does Bootstrap 5 itself hurt page speed?

    Not significantly on its own. The compiled and gzipped Bootstrap 5 CSS is around 22–30 KB, which is reasonable. The real issue is that commercial templates import the full Bootstrap build alongside dozens of plugin stylesheets without any tree-shaking or purging. Bootstrap 5’s SCSS architecture and ESM JavaScript bundle are specifically designed to support lean production builds — you just have to take advantage of them.

    Is PurgeCSS safe to use with Bootstrap 5 components?

    Yes, with one caveat: you must safelist dynamically injected class names. Bootstrap’s JavaScript adds classes like .show, .fade, .collapsing, .modal-open, and .tooltip at runtime. Since PurgeCSS only scans your HTML and JS source files, these dynamic classes can be stripped unless you explicitly whitelist them. Build your safelist from Bootstrap’s JavaScript source to make sure you catch everything.

    Should I use a CDN to serve Bootstrap CSS and JS?

    In 2026, the answer is generally no for CSS, and maybe for JS. The theory that users already have Bootstrap cached from the jsDelivr CDN doesn’t hold in practice — HTTP/2 cache partitioning means cross-site caches are isolated. You’re better off self-hosting a purged, custom-built stylesheet that’s far smaller than the CDN version. For a standalone utility script like Popper.js, a CDN can still offer latency benefits if it’s geographically closer to your users, but only if you’re not already using a CDN of your own.

    What is the single biggest performance improvement I can make right now?

    Convert your hero and above-the-fold images to AVIF or WebP, add explicit width and height attributes, and set fetchpriority="high" on the LCP image. This alone typically improves LCP by 50–70% and eliminates layout shift. It requires no build tooling and can be done in under an hour on a typical landing page template.

    How do Bootstrap 5 JavaScript components like modals and accordions affect performance?

    Each Bootstrap component adds some JavaScript weight, but the bigger risk is initialising all components on page load regardless of whether they appear on the page. Use the ESM import pattern to import only the components each page actually uses, and initialise them lazily where possible — for example, initialising a modal only when the trigger button is first clicked rather than at DOMContentLoaded. For details on how modals and accordions are structured, see our guides on Bootstrap 5 Modal triggers and animations and Bootstrap 5 Accordion and Collapse.


    Ready to Build a Fast Bootstrap 5 Site?

    Canvas Template ships with full SCSS source files, ESM JavaScript, and a structured build pipeline so every optimisation technique in this guide works out of the box. No retrofitting required.

    If you want to skip the build configuration entirely and get a performance-optimised site live faster, try CanvasBuilder — the AI-powered website builder built on the Canvas design system, with image optimisation and CSS purging handled automatically.

    Explore Canvas Template
    Try CanvasBuilder Free

  • Bootstrap 5 Modal: Triggers, Sizes, and Custom Animations

    Bootstrap 5 Modal: Triggers, Sizes, and Custom Animations

    Bootstrap 5 Modal: Triggers, Sizes, and Custom Animations

    Bootstrap 5 Modal: Triggers, Sizes, and Custom Animations

    The modal is one of the most widely used UI patterns on the web — confirmation dialogs, image lightboxes, lead capture forms, cookie consent banners — nearly every production site uses one. Bootstrap 5 ships with a capable, accessible bootstrap dialog component right out of the box, but most developers only scratch the surface of what it can do.

    In this bootstrap modal tutorial, you will go from the basic trigger pattern all the way through responsive sizing, stacked modals, custom CSS animations, and JavaScript event hooks. Every section includes working code you can drop straight into a project. If you are already building on a solid foundation like Canvas Template, most of these patterns will slot in with minimal adjustment because the template follows Bootstrap 5 conventions throughout.

    Let us get into it.

    Key Takeaways

    • Bootstrap 5 modals are triggered via HTML data attributes or the JavaScript API — both approaches are covered with working examples.
    • Five official size modifiers exist: small, default, large, extra-large, and fullscreen (with responsive breakpoint variants).
    • You can replace the default fade animation with custom CSS keyframes without touching Bootstrap source files.
    • The JavaScript event lifecycle (show.bs.modal, shown.bs.modal, hide.bs.modal, hidden.bs.modal) gives you fine-grained control over modal behaviour.
    • Accessibility is non-negotiable: always include aria-labelledby, aria-modal, and correct focus management.
    • Canvas Template pre-wires many of these patterns so you can customise rather than build from scratch.

    The Anatomy of a Bootstrap 5 Modal

    Before you add any custom behaviour, you need a clean understanding of the markup structure Bootstrap 5 expects. The component is split into three nested layers: the outer wrapper (.modal), a sizing/positioning container (.modal-dialog), and the visible surface (.modal-content). Inside .modal-content you optionally place a .modal-header, .modal-body, and .modal-footer.

    <!-- Trigger button -->
    <button
      type="button"
      class="btn btn-primary"
      data-bs-toggle="modal"
      data-bs-target="#exampleModal"
    >
      Open Modal
    </button>
    
    <!-- Modal markup -->
    <div
      class="modal fade"
      id="exampleModal"
      tabindex="-1"
      aria-labelledby="exampleModalLabel"
      aria-modal="true"
      role="dialog"
    >
      <div class="modal-dialog">
        <div class="modal-content">
    
          <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Modal Title</h5>
            <button
              type="button"
              class="btn-close"
              data-bs-dismiss="modal"
              aria-label="Close"
            ></button>
          </div>
    
          <div class="modal-body">
            <p>Your modal content goes here.</p>
          </div>
    
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
            <button type="button" class="btn btn-primary">Save changes</button>
          </div>
    
        </div>
      </div>
    </div>
    

    A few things worth highlighting: tabindex="-1" ensures the modal element itself is programmatically focusable without being part of the natural tab order. The aria-labelledby attribute connects the dialog to its title for screen readers. If you want to dig deeper into why these attributes matter, our post on making a Bootstrap 5 website accessible to WCAG 2.1 AA covers the full rationale and testing process.

    Bootstrap 5 dropped jQuery entirely, so the JavaScript API is now based on vanilla ES modules. You have two primary ways to open a modal: declarative HTML triggers and the programmatic JavaScript API.

    Data Attribute Triggers

    The simplest approach requires zero JavaScript. Add data-bs-toggle="modal" and data-bs-target="#yourModalId" to any clickable element:

    <!-- Works on buttons, anchors, or any element -->
    <a
      href="#"
      role="button"
      data-bs-toggle="modal"
      data-bs-target="#contactModal"
    >
      Contact Us
    </a>
    

    JavaScript API Triggers

    When you need conditional logic — for example, only open the modal after form validation passes — reach for the JavaScript API:

    // Get the modal element
    const modalEl = document.getElementById('confirmModal');
    
    // Instantiate (or retrieve existing instance)
    const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
    
    // Open programmatically
    document.getElementById('validateBtn').addEventListener('click', () => {
      const input = document.getElementById('userEmail').value;
      if (input.includes('@')) {
        modal.show();
      } else {
        alert('Please enter a valid email address.');
      }
    });
    
    // Close programmatically
    document.getElementById('confirmBtn').addEventListener('click', () => {
      modal.hide();
    });
    

    You can also pass configuration options on instantiation:

    const modal = new bootstrap.Modal(modalEl, {
      backdrop: 'static',  // click outside does NOT close
      keyboard: false,     // ESC key does NOT close
      focus: true          // auto-focus the modal on open
    });
    

    Setting backdrop: 'static' is particularly useful for confirmation dialogs where you want to force a deliberate user decision before the modal dismisses.

    The .modal-dialog container accepts size modifier classes that change the maximum width of the dialog. Bootstrap 5 also introduced responsive fullscreen variants that activate only below a specific breakpoint — a welcome improvement over the old all-or-nothing fullscreen approach.

    Class Max Width Breakpoint Behaviour
    .modal-sm 300px Fixed small dialog
    (default, no class) 500px Default responsive dialog
    .modal-lg 800px Large dialog
    .modal-xl 1140px Extra-large dialog
    .modal-fullscreen 100vw / 100vh Always fullscreen
    .modal-fullscreen-sm-down 100vw / 100vh Fullscreen below 576px
    .modal-fullscreen-md-down 100vw / 100vh Fullscreen below 768px
    .modal-fullscreen-lg-down 100vw / 100vh Fullscreen below 992px
    .modal-fullscreen-xl-down 100vw / 100vh Fullscreen below 1200px
    .modal-fullscreen-xxl-down 100vw / 100vh Fullscreen below 1400px

    The responsive fullscreen variants are especially useful for mobile-first modal forms. For example, a checkout form that should occupy the full screen on phones but behave as a centred dialog on desktop:

    <div class="modal-dialog modal-lg modal-fullscreen-md-down">
      <!-- Fullscreen on phones and tablets, large dialog on desktop -->
    </div>
    

    This mirrors the pattern used throughout Canvas Template’s prebuilt page sections, where form modals collapse gracefully on narrow viewports without a single media query written by hand.

    Two additional modifier classes on .modal-dialog address common layout problems: vertically centred modals and long-content scrollable modals.

    <!-- Vertically centred -->
    <div class="modal-dialog modal-dialog-centered"></div>
    
    <!-- Scrollable body (header and footer stay fixed) -->
    <div class="modal-dialog modal-dialog-scrollable"></div>
    
    <!-- Both at once -->
    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable"></div>
    

    The .modal-dialog-scrollable class is a practical solution for terms-and-conditions dialogs or long product descriptions where you want the header and footer actions to remain visible while the user scrolls through the body content. The same approach applies to the accordion-style expandable content patterns described in our deep-dive on the Bootstrap 5 Accordion and Collapse component.

    Custom Modal Animations with CSS Keyframes

    Bootstrap’s built-in .fade class applies a simple opacity transition. That works, but you can replace or augment it with custom keyframe animations without modifying Bootstrap source files — just override the relevant classes in your own stylesheet.

    Slide-in from the Top

    /* custom-modal.css */
    @keyframes modalSlideDown {
      from {
        transform: translateY(-60px);
        opacity: 0;
      }
      to {
        transform: translateY(0);
        opacity: 1;
      }
    }
    
    .modal.fade .modal-dialog {
      animation: none; /* remove Bootstrap's default transform */
    }
    
    .modal.show .modal-dialog {
      animation: modalSlideDown 0.35s ease forwards;
    }
    

    Scale / Zoom-in Effect

    @keyframes modalZoomIn {
      from {
        transform: scale(0.85);
        opacity: 0;
      }
      to {
        transform: scale(1);
        opacity: 1;
      }
    }
    
    .modal.show .modal-dialog {
      animation: modalZoomIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    }
    

    The cubic-bezier(0.34, 1.56, 0.64, 1) value produces a subtle spring overshoot that feels more polished than a linear ease. Keep animation durations between 250ms and 400ms — anything longer starts to feel sluggish, especially for power users who open modals frequently.

    Respecting User Preferences

    Always wrap custom animations in a reduced motion media query. This is a WCAG 2.1 requirement as well as good practice:

    @media (prefers-reduced-motion: reduce) {
      .modal.show .modal-dialog {
        animation: none;
      }
    }
    

    If you are theming Bootstrap with SCSS variables, you can tie animation durations to your existing token system. Our guide on using SCSS variables to theme a Bootstrap 5 site walks through exactly how to centralise these values so you only ever change them in one place.

    Bootstrap 5 fires four custom DOM events on the modal element throughout its lifecycle. Hooking into these lets you do things like lazy-load content, reset forms, send analytics events, or prevent a modal from closing until an async operation completes.

    Event Fires When Cancellable
    show.bs.modal Immediately when show() is called Yes — call event.preventDefault()
    shown.bs.modal After the modal is fully visible (transition complete) No
    hide.bs.modal Immediately when hide() is called Yes — call event.preventDefault()
    hidden.bs.modal After the modal is fully hidden (transition complete) No
    const modalEl = document.getElementById('asyncModal');
    
    // Lazy-load modal content on show
    modalEl.addEventListener('show.bs.modal', async (event) => {
      const body = modalEl.querySelector('.modal-body');
      body.innerHTML = '<div class="text-center"><div class="spinner-border"></div></div>';
    
      const response = await fetch('/api/modal-content');
      const html = await response.text();
      body.innerHTML = html;
    });
    
    // Reset a form after the modal fully closes
    modalEl.addEventListener('hidden.bs.modal', () => {
      const form = modalEl.querySelector('form');
      if (form) form.reset();
    });
    
    // Prevent close until user confirms
    modalEl.addEventListener('hide.bs.modal', (event) => {
      const isDirty = modalEl.querySelector('form')?.dataset.dirty === 'true';
      if (isDirty && !confirm('You have unsaved changes. Close anyway?')) {
        event.preventDefault();
      }
    });
    

    The relatedTarget property on the show.bs.modal event gives you a reference to the element that triggered the modal. This is invaluable for dynamic modals where one dialog serves multiple data sources — for example, an edit modal in a data table:

    modalEl.addEventListener('show.bs.modal', (event) => {
      const triggerButton = event.relatedTarget;
      const userId = triggerButton.dataset.userId;
      const userName = triggerButton.dataset.userName;
    
      modalEl.querySelector('.modal-title').textContent = `Edit: ${userName}`;
      modalEl.querySelector('#userId').value = userId;
    });
    

    A bootstrap dialog component that traps keyboard users or fails to announce itself to screen readers is not just a UX problem — it can create legal exposure under accessibility regulations that are increasingly enforced in 2026. Here is the minimum checklist:

    • Always include role="dialog" and aria-modal="true" on the outer .modal element. Bootstrap adds these automatically when using standard markup, but verify they are present if you are using a custom structure.
    • Link the dialog to its title using aria-labelledby pointing to the .modal-title element’s ID.
    • For dialogs without a visible title, use aria-label directly on the .modal element with a descriptive string.
    • Ensure focus moves into the modal when it opens. Bootstrap does this by default via the focus: true option. Do not override this behaviour carelessly.
    • Trap focus within the modal while it is open. Bootstrap 5 handles this natively — do not break it by manipulating tabindex on elements inside the modal in ways that allow focus to escape to the background.
    • Return focus to the trigger element after the modal closes. Again, Bootstrap handles this automatically — the trigger button regains focus when the modal hides.
    <!-- Accessible alert/confirmation dialog pattern -->
    <div
      class="modal fade"
      id="deleteConfirmModal"
      tabindex="-1"
      role="alertdialog"
      aria-modal="true"
      aria-labelledby="deleteModalTitle"
      aria-describedby="deleteModalDesc"
    >
      <div class="modal-dialog modal-dialog-centered modal-sm">
        <div class="modal-content">
          <div class="modal-header border-0">
            <h5 class="modal-title" id="deleteModalTitle">Delete Item?</h5>
          </div>
          <div class="modal-body" id="deleteModalDesc">
            This action cannot be undone.
          </div>
          <div class="modal-footer border-0">
            <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
              Cancel
            </button>
            <button type="button" class="btn btn-danger" id="confirmDeleteBtn">
              Delete
            </button>
          </div>
        </div>
      </div>
    </div>
    

    Note the use of role="alertdialog" for destructive confirmation dialogs — this signals greater urgency to assistive technologies compared to the standard role="dialog".

    Understanding the API is one thing. Knowing which pattern to reach for in a given design scenario is what separates good implementations from great ones.

    Video Lightbox Modal

    <!-- Stop video playback when modal closes -->
    <div class="modal fade" id="videoModal" tabindex="-1" aria-modal="true" aria-label="Product video">
      <div class="modal-dialog modal-lg modal-dialog-centered">
        <div class="modal-content bg-black border-0">
          <div class="modal-header border-0 pb-0">
            <button type="button" class="btn-close btn-close-white ms-auto" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body p-0">
            <div class="ratio ratio-16x9">
              <iframe
                id="videoFrame"
                src=""
                data-src="https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1"
                title="Product video"
                allowfullscreen
              ></iframe>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <script>
    const videoModal = document.getElementById('videoModal');
    const videoFrame = document.getElementById('videoFrame');
    
    videoModal.addEventListener('show.bs.modal', () => {
      videoFrame.src = videoFrame.dataset.src;
    });
    
    videoModal.addEventListener('hidden.bs.modal', () => {
      videoFrame.src = '';
    });
    </script>
    

    Multi-Step Form Modal

    For multi-step flows inside a modal, use Bootstrap’s own d-none / d-block utilities to show and hide steps, updating an ARIA live region to announce progress to screen readers:

    <div class="modal-body">
      <div aria-live="polite" class="visually-hidden" id="stepAnnouncer"></div>
    
      <div id="step1">
        <!-- Step 1 fields -->
        <button type="button" class="btn btn-primary" id="toStep2">Next &rarr;</button>
      </div>
    
      <div id="step2" class="d-none">
        <!-- Step 2 fields -->
        <button type="button" class="btn btn-secondary" id="toStep1">&larr; Back</button>
        <button type="submit" class="btn btn-success">Submit</button>
      </div>
    </div>
    
    <script>
    document.getElementById('toStep2').addEventListener('click', () => {
      document.getElementById('step1').classList.add('d-none');
      document.getElementById('step2').classList.remove('d-none');
      document.getElementById('stepAnnouncer').textContent = 'Step 2 of 2';
    });
    </script>
    

    If you are building marketing landing pages that convert, these modal patterns pair naturally with the conversion-focused sections in Canvas Template. You can also prototype entire flows visually using CanvasBuilder, the AI-powered website builder that generates Bootstrap 5 HTML you can customise immediately.


    Frequently Asked Questions

    How do I prevent a Bootstrap 5 modal from closing when clicking the backdrop?

    Pass backdrop: 'static' when instantiating the modal via JavaScript, or add the attribute data-bs-backdrop="static" to the outer .modal element. Combined with data-bs-keyboard="false" (or keyboard: false in JS), the modal will only close via an explicit dismiss button or programmatic modal.hide() call.

    Can I open one Bootstrap modal from inside another?

    Technically yes, but nested modals are not officially supported and can cause z-index and scroll-lock conflicts. The recommended pattern is to close the first modal programmatically with modal.hide(), then in the hidden.bs.modal event handler, open the second one. This avoids stacking issues and keeps the ARIA hierarchy clean.

    How do I pass dynamic data to a Bootstrap 5 modal?

    Use data-* attributes on the trigger element and read them via event.relatedTarget.dataset inside the show.bs.modal event listener. This is the pattern shown in the JavaScript events section above and is the most maintainable approach for table-driven edit dialogs or product detail modals.

    Why is my custom modal animation not working?

    The most common cause is that Bootstrap’s built-in transform on .modal.fade .modal-dialog is overriding your keyframe. Add animation: none on .modal.fade .modal-dialog first to clear the default, then apply your keyframe on .modal.show .modal-dialog. Also confirm your stylesheet loads after the Bootstrap CSS so your rules have higher specificity.

    Does Bootstrap 5 support accessible modal dialogs out of the box?

    Bootstrap 5 does a solid job with focus trapping, focus return, and aria-modal — but you are still responsible for providing correct aria-labelledby or aria-label attributes, using role="alertdialog" for destructive confirmations, and honouring prefers-reduced-motion in any custom animations. See our full walkthrough on Bootstrap 5 accessibility for WCAG 2.1 AA for a complete audit checklist.


    Conclusion

    The bootstrap 5 modal is deceptively powerful. The default fade-in and centred dialog covers 80% of use cases, but the remaining 20% — dynamic data loading, custom animations, responsive fullscreen behaviour, accessible confirmation dialogs — is where your implementation either feels production-grade or looks like it was cobbled together from Stack Overflow snippets.

    The patterns in this bootstrap modal tutorial give you a repeatable toolkit: clean anatomy, flexible size modifiers, JavaScript event hooks for async workflows, and CSS keyframes that respect user motion preferences. Combined with a well-structured foundation, these building blocks let you ship polished UI fast.

    Ready to build faster? Canvas Template gives you a professionally designed Bootstrap 5 HTML template with dozens of pre-built sections — including modal-powered contact forms, video lightboxes, and confirmation dialogs — that you can customise rather than build from zero. If you want to prototype your next project visually before writing any code, CanvasBuilder generates clean Bootstrap 5 HTML in minutes using AI, giving you a head start you can take straight into production.

  • How to Make a Bootstrap 5 Website Accessible (WCAG 2.1 AA)

    How to Make a Bootstrap 5 Website Accessible (WCAG 2.1 AA)

    How to Make a Bootstrap 5 Website Accessible (WCAG 2.1 AA)

    How to Make a Bootstrap 5 Website Accessible (WCAG 2.1 AA)

    Bootstrap 5 ships with a solid accessibility foundation — semantic HTML, keyboard-navigable components, and a growing set of ARIA attributes baked in. But “good by default” is not the same as “compliant by default.” If you hand a vanilla Bootstrap 5 build to an accessibility auditor and expect a clean WCAG 2.1 AA report, you are going to be disappointed.

    The good news: closing the gap between a standard Bootstrap 5 site and a fully WCAG 2.1 AA-compliant one is entirely achievable without rewriting your markup from scratch. In this guide you will learn exactly where Bootstrap needs a hand, which patterns to use, and how to validate your work. Every technique here applies whether you are building from a blank slate or customising a premium template like Canvas Template.

    Key Takeaways

    • Bootstrap 5 provides a good accessibility baseline but requires deliberate additions to reach WCAG 2.1 AA compliance.
    • Colour contrast, focus management, ARIA labels, and skip links are the four biggest gaps in most Bootstrap projects.
    • Interactive components — modals, accordions, dropdowns — each need specific ARIA attributes and keyboard-interaction patterns.
    • Accessible colour theming can be handled systematically through SCSS variables, keeping changes maintainable.
    • Automated tools (axe, Lighthouse) catch roughly 30–40% of issues; manual keyboard and screen-reader testing is non-negotiable.
    • Accessible websites rank better, convert better, and protect your clients from legal exposure — accessibility is a business argument, not just a moral one.

    Why Accessibility Matters for Bootstrap Projects in 2026

    Accessibility legislation has accelerated dramatically. The European Accessibility Act came into full force for private-sector digital products in mid-2025, the ADA continues to generate thousands of lawsuits annually in the United States, and the UK’s accessibility regulations now extend beyond the public sector. In 2026, ignoring WCAG 2.1 AA is a legal and commercial risk, not just a best-practice checkbox.

    Beyond compliance, accessible websites perform better. Screen-reader-friendly markup is semantic markup, which search engines love. High colour contrast improves readability for everyone — including users on low-brightness displays or in sunlight. Keyboard navigation benefits power users and people with motor disabilities alike. If you have been shopping for a starting point, it is worth checking out our roundup of the best free Bootstrap 5 templates for agencies in 2026 — accessibility quality varies significantly between options, so auditing before you commit saves hours later.

    WCAG 2.1 AA is organised around four principles — Perceivable, Operable, Understandable, and Robust (POUR). Every technique in this article maps to one or more of those principles.

    Colour Contrast and Accessible Theming

    WCAG 2.1 Success Criterion 1.4.3 requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text (18pt / 14pt bold or above). Bootstrap 5’s default colour palette is close but not universally compliant — particularly the muted text utilities (.text-muted, .text-secondary) and some contextual colours against white backgrounds.

    The cleanest way to fix this at scale is through SCSS variables. Instead of hunting individual utility classes, override the source variables before Bootstrap’s own files compile:

    // _custom-variables.scss
    
    // Boost contrast on muted text — default #6c757d fails 4.5:1 on white
    $text-muted: #5a6472;
    
    // Ensure secondary colour passes 4.5:1 on white (#fff)
    $secondary: #5a6472;
    
    // Override danger — Bootstrap default #dc3545 passes, but check your tints
    $danger: #b91c1c;
    
    // Ensure link colour is accessible
    $link-color: #0056b3;
    $link-hover-color: #003d80;
    

    Always verify your final rendered values — not just the variable — using a tool like the WebAIM Contrast Checker or axe DevTools. For a deeper dive into Bootstrap theming with SCSS, our guide on how to use SCSS variables to theme a Bootstrap 5 site covers the full workflow.

    Do not forget non-text contrast (SC 1.4.11). Form control borders, focus rings, icon-only buttons, and custom checkbox/radio borders all need at least 3:1 against their adjacent background. Bootstrap 5’s default focus ring was improved in recent releases but can still fall short on coloured backgrounds:

    /* Globally strengthen the focus ring */
    :focus-visible {
      outline: 3px solid #0056b3;
      outline-offset: 3px;
    }
    
    /* Remove the weaker default only when your custom ring is present */
    :focus:not(:focus-visible) {
      outline: none;
    }
    

    Keyboard-only users and screen-reader users rely on skip links to bypass repetitive navigation and jump straight to main content. Bootstrap does not include a skip link by default — you must add one yourself. Place it as the very first element inside <body>:

    <a class="visually-hidden-focusable" href="#main-content">
      Skip to main content
    </a>
    
    <!-- ... navigation ... -->
    
    <main id="main-content" tabindex="-1">
      <!-- page content -->
    </main>
    

    Bootstrap’s own .visually-hidden-focusable utility (formerly .sr-only-focusable in v4) hides the link visually until it receives focus, then renders it. The tabindex="-1" on <main> allows programmatic focus without placing it in the natural tab order.

    Beyond skip links, ensure your page uses proper landmark elements. Screen-reader users navigate by landmarks the way sighted users scan a page visually:

    <header role="banner">
      <nav aria-label="Primary navigation">...</nav>
    </header>
    
    <main id="main-content" tabindex="-1">
    
      <section aria-labelledby="features-heading">
        <h2 id="features-heading">Features</h2>
        ...
      </section>
    
      <aside aria-label="Related resources">...</aside>
    
    </main>
    
    <footer role="contentinfo">...</footer>
    

    If you have multiple <nav> elements on a page — primary navigation, breadcrumbs, footer links — give each a unique aria-label so users can distinguish them in landmark menus.

    Making Bootstrap’s Interactive Components Accessible

    Bootstrap 5’s JavaScript components handle a reasonable amount of ARIA state management automatically, but several gaps require developer attention.

    Modals

    Bootstrap modals move focus to the dialog on open and trap it within the modal until it closes — both correct behaviours. What often breaks in practice is the aria-labelledby connection between the modal container and its heading:

    <div class="modal fade" id="contactModal" tabindex="-1"
         aria-labelledby="contactModalLabel" aria-modal="true" role="dialog">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h2 class="modal-title fs-5" id="contactModalLabel">Contact Us</h2>
            <button type="button" class="btn-close" data-bs-dismiss="modal"
                    aria-label="Close contact form"></button>
          </div>
          <div class="modal-body">...</div>
        </div>
      </div>
    </div>
    

    Notice aria-modal="true" — this tells screen readers that content outside the dialog is inert. Also note the descriptive aria-label on the close button rather than the default empty string.

    Accordions

    Bootstrap 5’s accordion component already uses aria-expanded and aria-controls. Your main job is ensuring the heading level inside each accordion button matches your page outline. If your accordion lives inside an <h2> section, the accordion triggers should be <h3>:

    <div class="accordion" id="faqAccordion">
      <div class="accordion-item">
        <h3 class="accordion-header">
          <button class="accordion-button" type="button"
                  data-bs-toggle="collapse" data-bs-target="#faq1"
                  aria-expanded="true" aria-controls="faq1">
            What is your return policy?
          </button>
        </h3>
        <div id="faq1" class="accordion-collapse collapse show"
             data-bs-parent="#faqAccordion">
          <div class="accordion-body">...</div>
        </div>
      </div>
    </div>
    

    For a deeper look at accordion patterns and all collapse variants, see our comprehensive guide to Bootstrap 5 accordion and collapse — just verify that heading levels suit your specific page structure when you implement any pattern.

    Dropdowns and Navbars

    Dropdown menus should be operable by keyboard alone. Bootstrap’s dropdown already responds to arrow keys when open, but make sure every trigger has a clear label and that icon-only navbar items are not left unlabelled:

    <!-- Icon-only button inside navbar: always add aria-label -->
    <button class="navbar-toggler" type="button"
            data-bs-toggle="collapse" data-bs-target="#navbarNav"
            aria-controls="navbarNav" aria-expanded="false"
            aria-label="Toggle navigation menu">
      <span class="navbar-toggler-icon"></span>
    </button>
    

    Accessible Forms and Error Handling

    Forms are one of the most common sources of accessibility failures. Bootstrap’s form classes are visually polished but require explicit label associations and meaningful error messages to be usable without sight.

    <div class="mb-3">
      <label for="userEmail" class="form-label">
        Email address <span aria-hidden="true" class="text-danger">*</span>
        <span class="visually-hidden">(required)</span>
      </label>
      <input type="email" class="form-control is-invalid" id="userEmail"
             aria-describedby="emailHelp emailError" required
             autocomplete="email">
      <div id="emailHelp" class="form-text">We’ll never share your email.</div>
      <div id="emailError" class="invalid-feedback" role="alert">
        Please enter a valid email address.
      </div>
    </div>
    

    Key patterns here: the label is explicitly associated via matching for and id; aria-describedby chains both the hint and the error message; role="alert" on the error causes screen readers to announce it immediately without requiring focus; the asterisk is hidden from assistive technology with aria-hidden="true" while a visually hidden text alternative conveys “required”.

    For multi-step forms or pages with complex layouts, also consider aria-live regions to announce dynamic status updates — for example, a character counter or an async validation result — without relocating focus.

    Images, Icons, and Media Accessibility

    Every <img> must have an alt attribute. Decorative images get alt=""; informative images get concise, meaningful descriptions; functional images (like linked images) describe the destination or action. Bootstrap does not enforce this — it is entirely your responsibility.

    <!-- Informative image -->
    <img src="dashboard-screenshot.png" alt="Canvas Template admin dashboard showing analytics widgets" class="img-fluid">
    
    <!-- Decorative image -->
    <img src="background-pattern.svg" alt="" class="img-fluid" aria-hidden="true">
    
    <!-- Icon used without adjacent visible text -->
    <button class="btn btn-primary">
      <i class="bi bi-envelope" aria-hidden="true"></i>
      <span class="visually-hidden">Send email</span>
    </button>
    

    For SVG icons, add aria-hidden="true" when they are purely decorative. When an SVG is the sole content of a link or button, use title inside the SVG plus aria-labelledby on the element referencing it.

    For video content, WCAG 2.1 AA requires captions for pre-recorded audio and audio descriptions for pre-recorded video where the visual content is not conveyed by the audio track. At minimum, host captions on every embedded video and include a transcript link nearby.

    Testing Your Bootstrap Site for Accessibility

    Automated testing is the starting point, not the finish line. Here is a layered testing approach:

    Tool / Method What It Catches Effort
    axe DevTools (browser extension) ~30–40% of WCAG issues automatically; colour contrast, missing labels, ARIA errors Low
    Lighthouse (Chrome DevTools) Overlaps with axe; useful for CI integration Low
    WAVE (WebAIM) Visual overlay of errors, alerts, and structure Low
    Keyboard-only navigation Focus order, keyboard traps, visible focus indicator, skip links Medium
    Screen reader (NVDA + Firefox / VoiceOver + Safari) Real user experience; ARIA, live regions, heading structure High
    Manual expert audit Cognitive load, error recovery, complex interactions High

    Run automated checks on every pull request if possible — axe-core integrates with Jest, Cypress, and Playwright. Reserve full manual testing for major releases or before client handoff. If you are using CanvasBuilder to scaffold pages quickly, run axe over the generated output before you treat it as production-ready, just as you would with any generated code.

    WCAG 2.1 AA Quick Reference for Bootstrap Developers

    The table below maps the most commonly failed WCAG 2.1 AA success criteria to concrete Bootstrap-specific fixes:

    WCAG SC Criterion Bootstrap Fix
    1.1.1 Non-text Content Always provide meaningful alt on <img>; aria-hidden on decorative icons
    1.3.1 Info and Relationships Use semantic HTML; associate labels with inputs; use aria-labelledby on sections
    1.4.3 Contrast (Minimum) Override $text-muted and $secondary in SCSS; verify all custom colours
    1.4.11 Non-text Contrast Strengthen form borders and focus rings to 3:1 against background
    2.1.1 Keyboard Verify all interactive components keyboard-accessible; add skip link
    2.4.3 Focus Order Ensure DOM order matches visual order; manage focus on modal open/close
    2.4.6 Headings and Labels Use a logical heading hierarchy; match accordion heading levels to page outline
    2.4.7 Focus Visible Override :focus-visible with high-contrast 3px outline
    3.3.1 Error Identification Use role="alert" on .invalid-feedback; chain errors via aria-describedby
    4.1.2 Name, Role, Value Add aria-label / aria-labelledby to all custom widgets; use native HTML elements where possible

    Frequently Asked Questions

    Is Bootstrap 5 WCAG 2.1 AA compliant out of the box?

    Not fully. Bootstrap 5 handles several accessibility concerns automatically — focus trapping in modals, ARIA state on accordions, semantic button elements — but colour contrast on utility classes like .text-muted, the absence of skip links, and gaps in ARIA labelling on interactive components mean that additional work is required before a site can credibly claim WCAG 2.1 AA compliance.

    What is the fastest way to check colour contrast in a Bootstrap project?

    Install the axe DevTools browser extension and run it on your rendered pages — it will flag contrast failures automatically. For systematic fixes, override Bootstrap SCSS variables before compilation rather than patching individual classes. Compile, render, then re-run axe to confirm the overrides resolved the issues on actual text and UI components.

    Do I need to test with a real screen reader, or is automated testing enough?

    Automated tools like axe and Lighthouse catch roughly 30–40% of real-world accessibility issues. Screen-reader testing is essential for verifying ARIA live regions, focus management, dynamic content announcements, and the overall user experience. Test with at least two combinations: NVDA with Firefox and VoiceOver with Safari, as behaviour differs between them.

    How do I handle dynamic content loaded via JavaScript (AJAX) in a Bootstrap 5 site?

    Use aria-live regions to announce dynamic updates. For non-urgent updates use aria-live="polite"; for critical alerts use aria-live="assertive". After content loads, programmatically move focus to the new content or its container using .focus() on an element with tabindex="-1". Bootstrap’s own toast and alert components use these patterns as a reference.

    Does making my Bootstrap site accessible negatively affect performance or aesthetics?

    No — and in most cases it actively improves both. Semantic HTML reduces markup complexity. ARIA attributes add negligible byte weight. Stronger colour contrast is objectively more readable for sighted users. Descriptive alt text and heading structures improve SEO. Accessibility and good design are not in tension; they reinforce each other when approached from the start rather than bolted on at the end.


    Building an accessible Bootstrap 5 site in 2026 is not optional — it is a professional standard. The techniques above cover the most impactful changes: SCSS-driven contrast fixes, skip links and landmarks, correctly labelled interactive components, accessible form error patterns, and a layered testing workflow. None of them require abandoning Bootstrap’s component model; they require understanding it deeply enough to extend it responsibly.

    If you want a head start with a template that is already built with accessibility best practices in mind, explore Canvas Template — a premium Bootstrap 5 HTML template with clean semantic markup, proper heading hierarchies, and ARIA-ready components. And if you want to scaffold a full site even faster while keeping accessibility in the loop, CanvasBuilder lets you generate Bootstrap 5 pages visually, giving you a production-quality starting point you can then audit and refine.

  • Best Free Bootstrap 5 Templates for Agencies in 2026

    Best Free Bootstrap 5 Templates for Agencies in 2026

    Best Free Bootstrap 5 Templates for Agencies in 2026

    Best Free Bootstrap 5 Templates for Agencies in 2026

    Agency websites carry a heavy burden. They need to communicate credibility, showcase portfolio work, convert visitors into leads, and load fast enough that a prospective client doesn’t bounce before they even read a headline. In 2026, the bar is higher than ever — and yet a significant number of agencies are still launching sites built on bloated WordPress themes or hand-rolled CSS that nobody on the team can maintain.

    Bootstrap 5 solves a large chunk of that problem. It ships with a mature grid, utility-first classes, accessible components out of the box, and a JavaScript plugin layer that no longer depends on jQuery. Whether you’re a solo freelancer building your first agency site or a 20-person studio spinning up a new microsite for a subsidiary brand, a well-chosen Bootstrap 5 template can get you from blank canvas to polished prototype in hours — not weeks.

    This guide covers the best free Bootstrap 5 templates for agencies in 2026, what to look for before you download, how to evaluate template quality at the code level, and when it makes sense to upgrade to a premium option. Let’s get into it.

    Key Takeaways

    • Free Bootstrap 5 templates can dramatically reduce agency site build time when chosen carefully — but template quality varies wildly.
    • Look for semantic HTML, clean SCSS architecture, and proper use of Bootstrap 5 utility classes rather than inline styles.
    • Agency templates specifically need strong hero sections, portfolio/case-study layouts, service grids, and a contact or lead-capture section.
    • The difference between free and premium templates often comes down to component depth, documentation quality, and long-term maintenance.
    • Tools like CanvasBuilder.co let you generate a Bootstrap 5 site with AI and then customise the output — a strong middle ground between templates and custom builds.
    • Always test a template’s mobile responsiveness, accessibility score, and page speed before committing to it for a client project.

    What Makes a Great Agency Bootstrap 5 Template?

    Not all free Bootstrap templates are created equal. Some are little more than a single HTML file with a navigation bar, a jumbotron, and a footer slapped together. Others are genuinely production-ready starting points. Before you grab the first result from a Google search, here’s what to actually evaluate.

    Semantic, accessible HTML. Screen readers, search engines, and browser dev tools all care deeply about whether your markup is meaningful. An agency site built on <div> soup will cost you SEO points and accessibility compliance. Look for proper use of <header>, <main>, <section>, <article>, and <footer> elements, plus ARIA labels on interactive components.

    Bootstrap 5 — not Bootstrap 4 relabelled. It sounds obvious, but a surprising number of “Bootstrap 5” templates on free directories are actually Bootstrap 4 templates with a version number swap in the CDN link and nothing else updated. Check that the template uses data-bs-* attributes (not the old data-* ones), uses g-* gutter classes, and doesn’t import jQuery.

    SCSS source files. A template that ships only minified CSS is a dead end the moment a client asks for brand colours. Good templates include an SCSS layer with custom properties or Bootstrap SCSS variable overrides. If you haven’t worked with this pattern before, our guide on how to use SCSS variables to theme a Bootstrap 5 site is an excellent primer.

    Component variety relevant to agencies. At minimum, an agency template should include: a hero section, services grid, portfolio/work grid, testimonials, team section, pricing table, contact form, and a blog listing layout. If any of those are missing, you’ll be building them from scratch.

    Top Free Bootstrap 5 Templates for Agencies in 2026

    The following templates have been evaluated against the criteria above. Each has been checked for Bootstrap 5 compatibility, mobile responsiveness, and suitability for agency use cases.

    1. Agencio (Bootstrap 5 Agency Template)

    Agencio is a clean, single-page agency template with a strong hero, service cards, portfolio grid, and a sticky navigation. It uses Bootstrap 5’s grid correctly, applies utility classes consistently, and avoids the custom CSS avalanche that makes many free templates unmaintainable. It’s a solid starting point for a small agency that wants something polished with minimal customisation effort.

    2. Squadfree

    Squadfree targets creative agencies and digital studios. Its standout feature is a multi-layout portfolio section with filter functionality built on Bootstrap 5’s existing JavaScript layer — no additional library required. It also ships with a dark mode variant using CSS custom properties, which saves meaningful development time.

    3. Designstudio

    This template is more minimal — think design-led agency rather than full-service digital shop. The typography system is well-considered, and the card components follow Bootstrap 5 conventions properly. If you’re looking for card layout inspiration, it pairs well with the patterns covered in our Bootstrap 5 card components guide.

    4. Meyawo

    Originally a personal portfolio template but widely used as a freelance agency starting point. Meyawo has strong section separation, animated counters, and a skills/service section that adapts nicely to agency positioning. The SCSS files are reasonably organised, though the variable naming convention doesn’t fully follow Bootstrap 5’s own naming patterns.

    5. Start Bootstrap — Agency

    Start Bootstrap’s Agency template is one of the most downloaded Bootstrap agency templates of all time — and the Bootstrap 5 version holds up well. It includes a portfolio lightbox, team grid, and contact form. The codebase is clean, well-commented, and actively maintained on GitHub. A safe default choice if you need something reliable and well-documented.

    Comparing Free Bootstrap 5 Agency Templates: At a Glance

    Template SCSS Source Dark Mode Portfolio Section Blog Layout Accessibility Active Maintenance
    Agencio Yes No Yes No Good Moderate
    Squadfree Yes Yes Yes (filtered) No Good Active
    Designstudio Partial No Basic No Fair Low
    Meyawo Yes No Yes No Fair Low
    Start Bootstrap — Agency Yes No Yes (lightbox) No Very Good Active

    One pattern you’ll notice immediately: free agency templates almost universally lack a blog layout. For an agency that wants to publish thought leadership content — which is most of them — this means building that section from scratch or upgrading to a premium template that includes it.

    Customising Your Free Bootstrap Template: Key Code Patterns

    Once you’ve chosen a template, the real work begins. Here are the most common customisation tasks for agency sites and the Bootstrap 5 patterns that make them straightforward.

    Overriding Bootstrap’s colour system is almost always the first task. Rather than hunting through CSS files, use SCSS variable overrides before the Bootstrap import:

    // _custom-variables.scss
    $primary: #1a1aff;
    $secondary: #f5f5f0;
    $body-bg: #ffffff;
    $body-color: #1c1c1e;
    $font-family-base: 'Inter', sans-serif;
    $border-radius: 0.375rem;
    $border-radius-lg: 0.75rem;
    
    // Then import Bootstrap
    @import "bootstrap/scss/bootstrap";
    

    Building a services grid is a core agency section. Bootstrap 5’s column classes and gap utilities make this clean:

    <section class="py-6">
      <div class="container">
        <div class="row g-4">
          <div class="col-12 col-md-6 col-lg-4">
            <div class="card h-100 border-0 shadow-sm p-4">
              <div class="card-body">
                <span class="fs-1 mb-3 d-block">&#128187;</span>
                <h3 class="h5 fw-semibold">Web Design</h3>
                <p class="text-muted">
                  Human-centred design systems built for conversion
                  and long-term scalability.
                </p>
              </div>
            </div>
          </div>
          <!-- Repeat for additional services -->
        </div>
      </div>
    </section>
    

    A sticky, transparent-to-solid navbar is almost mandatory for agency sites. Bootstrap 5’s navbar combined with a small JavaScript listener handles this elegantly:

    <nav class="navbar navbar-expand-lg fixed-top" id="mainNav">
      <div class="container">
        <a class="navbar-brand fw-bold" href="#">AgencyName</a>
        <button class="navbar-toggler" type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarNav"
          aria-controls="navbarNav"
          aria-expanded="false"
          aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav ms-auto gap-2">
            <li class="nav-item"><a class="nav-link" href="#services">Services</a></li>
            <li class="nav-item"><a class="nav-link" href="#work">Work</a></li>
            <li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
          </ul>
        </div>
      </div>
    </nav>
    
    <script>
      const nav = document.getElementById('mainNav');
      window.addEventListener('scroll', () => {
        if (window.scrollY > 60) {
          nav.classList.add('bg-white', 'shadow-sm');
        } else {
          nav.classList.remove('bg-white', 'shadow-sm');
        }
      });
    </script>
    

    For a deeper look at navbar customisation patterns including mega menus and offcanvas mobile variants, see our dedicated post on Bootstrap 5 navbar customisation patterns.

    When Free Isn’t Enough: Premium vs Free for Agency Work

    Free templates are excellent starting points, but there are clear scenarios where the upgrade cost to a premium template pays for itself on the first project.

    Client-facing work with tight deadlines. Free templates rarely include more than a handful of pages. A premium template like Canvas — which ships with 40+ demo pages, a full component library, and detailed documentation — means you’re not building the blog layout, the pricing page, or the team section from scratch every time.

    Ongoing template maintenance. Free templates on GitHub can go unmaintained for years. If Bootstrap releases a point update that introduces a breaking change in a component, a premium template vendor has a business incentive to ship an update. With a free template, you’re on your own.

    SCSS architecture depth. Premium templates typically have a far more mature SCSS structure — separate partials for components, a dedicated variables file that maps cleanly onto Bootstrap’s own variable system, and utility extensions that save hours of custom CSS writing.

    If you’re weighing the broader decision between an HTML template and a WordPress theme for your agency, we’ve written an in-depth comparison that covers the real trade-offs: HTML template vs WordPress theme: which should you choose in 2026?

    Using AI to Generate and Customise Bootstrap Sites in 2026

    One development that’s changed the agency workflow significantly is the rise of AI-powered website builders that output clean, standards-compliant Bootstrap 5 code. CanvasBuilder.co sits in an interesting position here: it generates Bootstrap 5 HTML using AI and uses Canvas Template’s component library as the design foundation, which means the output doesn’t look like generic AI-generated markup — it looks like a designer built it.

    The practical workflow for agencies looks something like this:

    1. Use CanvasBuilder.co to generate a first-draft site structure based on your brief (agency type, services, colour preferences)
    2. Export the HTML/SCSS output
    3. Drop it into your development environment and refine with proper SCSS variable overrides and custom content
    4. Deploy as a static site or integrate with a headless CMS

    This approach is faster than starting from a free template and produces cleaner output than most free templates ship with by default. For agencies billing hourly, the time saving is material. For agencies on fixed-fee projects, it directly improves margin.

    Page Speed and Accessibility: Testing Your Chosen Template

    Downloading a template and pushing it to production without testing is a mistake that costs agencies client trust. Before any agency site goes live, run it through these checks:

    Lighthouse (Google Chrome DevTools). Target a Performance score of 90+, Accessibility of 90+, and Best Practices of 100. Most Bootstrap 5 templates will score well on Best Practices out of the box, but Performance often suffers from unoptimised images and unused CSS from the full Bootstrap bundle.

    To fix the unused CSS problem, import only the Bootstrap modules your template actually uses:

    // Only import what you need
    @import "bootstrap/scss/functions";
    @import "bootstrap/scss/variables";
    @import "bootstrap/scss/variables-dark";
    @import "bootstrap/scss/maps";
    @import "bootstrap/scss/mixins";
    @import "bootstrap/scss/utilities";
    @import "bootstrap/scss/root";
    @import "bootstrap/scss/reboot";
    @import "bootstrap/scss/type";
    @import "bootstrap/scss/grid";
    @import "bootstrap/scss/navbar";
    @import "bootstrap/scss/card";
    @import "bootstrap/scss/utilities/api";
    // Skip: tables, forms, modal, etc. if not used
    

    WAVE Accessibility Checker. Catches missing alt text, low contrast ratios, and form label associations that Lighthouse sometimes misses.

    WebPageTest. Gives you real-world network simulation data, including Time to First Byte (TTFB) and Largest Contentful Paint (LCP), which are core Web Vitals that affect Google Search ranking in 2026.

    Building on a Free Template: Practical Tips for Agencies

    If you’ve committed to a free template for a project, here are the practical habits that separate a clean, maintainable build from a codebase that becomes a maintenance nightmare six months later.

    Never edit Bootstrap’s source files directly. Always override via SCSS variables or custom CSS classes scoped to your project. This makes upgrading Bootstrap later a matter of bumping a package version rather than hunting down every place you touched the framework.

    Establish a component naming convention early. Bootstrap’s own classes are BEM-adjacent. Your custom components should follow a consistent pattern — for example, prefixing all custom components with your project initials: .ag-hero, .ag-service-card, .ag-testimonial.

    Use Bootstrap’s accordion component for FAQs and process sections. Agency sites frequently include “how we work” accordion sections. Bootstrap 5’s native accordion is accessible and requires zero additional JavaScript. If you need a deep dive into how it works, our Bootstrap 5 accordion and collapse guide covers every configuration option with live examples.

    Document your customisations. A short README in your project repo that lists which Bootstrap variables you’ve overridden and why saves the next developer (or future you) hours of detective work.


    Frequently Asked Questions

    Are free Bootstrap 5 agency templates good enough for real client projects?

    Yes — with caveats. Free templates are entirely appropriate for projects with modest scopes and tight budgets, as long as you evaluate the code quality before committing. Check for semantic HTML, proper Bootstrap 5 attribute conventions (data-bs-*), SCSS source files, and active maintenance on GitHub. For complex agency sites requiring 10+ pages, a custom content management system, or premium support, a paid template will typically save more time than it costs.

    What’s the difference between a Bootstrap 4 and Bootstrap 5 template for agencies?

    Bootstrap 5 drops the jQuery dependency entirely, introduces a new grid tier (xxl), replaces data-* attributes with data-bs-*, adds an expanded utility class system, and ships with improved form controls and RTL support. For agency sites specifically, the lack of jQuery dependency means faster page loads and one fewer security surface to manage. Always verify a template’s Bootstrap version before downloading — some “Bootstrap 5” templates in free directories are mislabelled.

    Can I use a free Bootstrap template for a commercial client project?

    Most free Bootstrap templates are distributed under the MIT License, which explicitly permits commercial use, modification, and redistribution. However, always read the specific license file included in the template repository. Some templates on free directories use Creative Commons licenses that require attribution or restrict commercial use.

    How do I add a portfolio filter to a Bootstrap 5 agency template?

    Bootstrap 5 doesn’t include native filtering functionality, but you can implement it with a small vanilla JavaScript snippet that toggles Bootstrap’s d-none utility class based on data-* category attributes on portfolio items. For more complex animated filtering, Isotope.js or a lightweight custom implementation using CSS grid transitions are common choices that don’t require jQuery.

    Is Canvas Template worth buying if good free agency templates exist?

    It depends on your workflow. If you build one agency site per year, a free template may be sufficient. If you’re an agency or freelancer building multiple client sites, Canvas Template’s depth — 40+ demo pages, extensive component library, SCSS architecture, and regular updates — pays for itself quickly. The comparison between Canvas and competing premium themes on ThemeForest is covered in detail in our post on Canvas Template vs Flatsome vs Avada.


    Ready to Build Something Better?

    Free templates are a great starting point, but if you’re building agency sites professionally, you need a template that keeps up with you. Canvas Template is a premium Bootstrap 5 HTML template on ThemeForest trusted by thousands of agencies, developers, and designers worldwide. It ships with 40+ demo pages, a full SCSS component library, and dedicated support.

    Explore Canvas Template
    Try CanvasBuilder AI Free

    CanvasBuilder.co uses AI to generate Bootstrap 5 sites built on Canvas Template’s design system — perfect for agencies that need a polished first draft fast.

  • Bootstrap 5 Accordion and Collapse: Everything You Need to Know

    Bootstrap 5 Accordion and Collapse: Everything You Need to Know

    Bootstrap 5 Accordion and Collapse: Everything You Need to Know

    Bootstrap 5 Accordion and Collapse: Everything You Need to Know

    Few UI patterns are more universally useful than the accordion. Whether you are building a FAQ section, a sidebar navigation tree, a product detail panel, or a settings interface, the ability to show and hide content on demand is fundamental to good user experience. Bootstrap 5 ships with two closely related components that handle exactly this: the collapse component and the accordion component. They share the same JavaScript engine under the hood, but serve distinct use cases.

    In this guide, you will get a thorough understanding of both components — how they work, how to customise them, how to control them with JavaScript, and how to avoid the common pitfalls that trip up developers working with Bootstrap 5 for the first time. Every concept comes with real, copy-paste-ready code you can drop straight into a project or into a premium HTML template like Canvas Template.

    Key Takeaways

    • Bootstrap 5 Collapse is a general-purpose show/hide component driven by data attributes or JavaScript.
    • Bootstrap 5 Accordion is a structured wrapper around Collapse that enforces single-panel-open behaviour by default.
    • Both components are fully accessible and keyboard-navigable out of the box.
    • You can control open/close behaviour, animate height transitions, and trigger multiple targets without writing a single line of JavaScript.
    • Custom SCSS variables let you restyle accordions to match any brand without touching the core Bootstrap source.
    • The always-open accordion variant removes the mutual-exclusivity restriction when you need multiple panels open simultaneously.

    How the Bootstrap 5 Collapse Component Works

    The collapse component is the foundation. At its simplest, it connects a trigger element (a button or anchor) to a target element via a shared ID or data-bs-target attribute. When the trigger is activated, Bootstrap toggles the show class on the target and animates its height from zero to its natural height using a CSS transition.

    The component fires four JavaScript events you can hook into: show.bs.collapse, shown.bs.collapse, hide.bs.collapse, and hidden.bs.collapse. This makes it easy to trigger side effects — updating icons, loading remote content, or logging analytics events — without fighting the framework.

    The bare minimum markup looks like this:

    <!-- Trigger -->
    <button
      class="btn btn-primary"
      type="button"
      data-bs-toggle="collapse"
      data-bs-target="#myCollapsePanel"
      aria-expanded="false"
      aria-controls="myCollapsePanel"
    >
      Toggle Panel
    </button>
    
    <!-- Target -->
    <div class="collapse" id="myCollapsePanel">
      <div class="card card-body">
        This content is hidden by default and revealed on click.
      </div>
    </div>

    Two things to note: first, the aria-expanded attribute is toggled automatically by Bootstrap — you do not need to manage it manually. Second, if you want the panel to start open, add the show class to the target element alongside collapse.

    Multiple Targets and Shared Triggers

    One less-discussed feature of the Bootstrap collapse component is its ability to target multiple elements from a single trigger, or share a single target across multiple triggers.

    One trigger, many targets: Pass a CSS selector string to data-bs-target that matches multiple elements.

    <button
      class="btn btn-secondary"
      type="button"
      data-bs-toggle="collapse"
      data-bs-target=".multi-collapse"
      aria-expanded="false"
    >
      Toggle All Panels
    </button>
    
    <div class="collapse multi-collapse" id="panelA">
      <div class="card card-body">Panel A content</div>
    </div>
    
    <div class="collapse multi-collapse" id="panelB">
      <div class="card card-body">Panel B content</div>
    </div>

    Many triggers, one target: Simply give multiple buttons the same data-bs-target value. Bootstrap handles the state synchronisation automatically, keeping aria-expanded consistent across all of them.

    If you are building complex interactive UIs — for instance, a sidebar that mirrors a mobile menu toggle — this shared-trigger pattern saves you a lot of custom JavaScript. It pairs particularly well with the layout techniques covered in our guide on building a landing page with Bootstrap 5 in under an hour.

    Building a Bootstrap 5 Accordion From Scratch

    The accordion component is essentially a styled group of collapse panels with a parent-awareness mechanism. When one panel opens, the others close — unless you opt out of that behaviour. Here is the full structural markup:

    <div class="accordion" id="faqAccordion">
    
      <!-- Item 1 -->
      <div class="accordion-item">
        <h2 class="accordion-header" id="headingOne">
          <button
            class="accordion-button"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#collapseOne"
            aria-expanded="true"
            aria-controls="collapseOne"
          >
            What is Bootstrap 5?
          </button>
        </h2>
        <div
          id="collapseOne"
          class="accordion-collapse collapse show"
          aria-labelledby="headingOne"
          data-bs-parent="#faqAccordion"
        >
          <div class="accordion-body">
            Bootstrap 5 is the most popular open-source CSS framework for
            building responsive, mobile-first websites.
          </div>
        </div>
      </div>
    
      <!-- Item 2 -->
      <div class="accordion-item">
        <h2 class="accordion-header" id="headingTwo">
          <button
            class="accordion-button collapsed"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#collapseTwo"
            aria-expanded="false"
            aria-controls="collapseTwo"
          >
            Does Bootstrap 5 require jQuery?
          </button>
        </h2>
        <div
          id="collapseTwo"
          class="accordion-collapse collapse"
          aria-labelledby="headingTwo"
          data-bs-parent="#faqAccordion"
        >
          <div class="accordion-body">
            No. Bootstrap 5 dropped the jQuery dependency entirely.
            It ships with its own vanilla JavaScript plugins.
          </div>
        </div>
      </div>
    
      <!-- Item 3 -->
      <div class="accordion-item">
        <h2 class="accordion-header" id="headingThree">
          <button
            class="accordion-button collapsed"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#collapseThree"
            aria-expanded="false"
            aria-controls="collapseThree"
          >
            Can I use Bootstrap 5 with SCSS?
          </button>
        </h2>
        <div
          id="collapseThree"
          class="accordion-collapse collapse"
          aria-labelledby="headingThree"
          data-bs-parent="#faqAccordion"
        >
          <div class="accordion-body">
            Absolutely. Bootstrap 5 ships full SCSS source files so you can
            override variables and extend styles at build time.
          </div>
        </div>
      </div>
    
    </div>

    The critical linking mechanism is data-bs-parent="#faqAccordion" on each collapse panel. This tells Bootstrap to close all sibling panels within the parent when any panel opens. Remove this attribute and all panels become independent — the “always open” variant discussed next.

    Accordion Variants: Flush Style and Always Open

    Bootstrap 5 ships two first-class accordion variants out of the box, controlled purely by class modifiers.

    Flush Accordion

    Add accordion-flush to the wrapper to remove the outer border and rounded corners, producing a flat, edge-to-edge style ideal for sidebars or cards with tight padding.

    <div class="accordion accordion-flush" id="sidebarAccordion">
      <!-- accordion items here -->
    </div>

    Always Open Accordion

    Simply omit the data-bs-parent attribute from every panel. Each panel then operates as an independent collapse, and multiple panels can be open simultaneously.

    <div class="accordion" id="alwaysOpenAccordion">
      <div class="accordion-item">
        <h2 class="accordion-header" id="aoHeadingOne">
          <button
            class="accordion-button"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#aoCollapseOne"
            aria-expanded="true"
            aria-controls="aoCollapseOne"
          >
            Section 1 (stays open independently)
          </button>
        </h2>
        <div id="aoCollapseOne" class="accordion-collapse collapse show">
          <div class="accordion-body">Content for section 1.</div>
        </div>
      </div>
    </div>

    Choosing between the two depends entirely on your content. Mutual-exclusivity works well for FAQs where answers are alternatives. Independent open panels work better for checklists, settings panels, or step-by-step instructions where a user may need to reference multiple sections at once.

    Collapse vs Accordion: Choosing the Right Component

    The table below summarises the key differences to help you choose the right tool for each situation.

    Feature Bootstrap Collapse Bootstrap Accordion
    Mutual exclusivity No (panels are independent) Yes (via data-bs-parent)
    Structural requirements Minimal — any trigger + target Requires .accordion, .accordion-item, .accordion-header, .accordion-body
    Built-in styling None (bring your own) Full: borders, chevron icon, padding, colours
    Best for Custom UI patterns, toggleable sections, mobile menus FAQs, documentation trees, settings panels
    Multiple triggers per panel Yes Yes
    JavaScript API Collapse class Collapse class (same engine)
    ARIA management Automatic Automatic

    Controlling Collapse and Accordion With JavaScript

    Data attributes get you far, but there are scenarios — dynamically generated content, SPA route changes, programmatic state resets — where you need direct JavaScript control. Bootstrap 5 exposes a clean class-based API for exactly this.

    // Get or create an instance
    const collapseEl = document.getElementById('myCollapsePanel');
    const bsCollapse = new bootstrap.Collapse(collapseEl, {
      toggle: false  // prevent auto-toggle on instantiation
    });
    
    // Programmatic control
    bsCollapse.show();
    bsCollapse.hide();
    bsCollapse.toggle();
    
    // Listen to events
    collapseEl.addEventListener('shown.bs.collapse', () => {
      console.log('Panel is fully open');
    });
    
    collapseEl.addEventListener('hidden.bs.collapse', () => {
      console.log('Panel is fully closed');
    });

    You can also retrieve an existing instance rather than creating a new one, which is important to avoid duplicate event binding:

    const existingInstance = bootstrap.Collapse.getInstance(collapseEl);
    if (existingInstance) {
      existingInstance.hide();
    }

    A common real-world pattern is collapsing all accordion panels at once — for example, when a “Reset” button is clicked in a settings page:

    document.getElementById('resetAllBtn').addEventListener('click', () => {
      document.querySelectorAll('.accordion-collapse.show').forEach(panel => {
        bootstrap.Collapse.getInstance(panel)?.hide();
      });
    });

    Customising the Accordion With SCSS

    Bootstrap 5’s accordion ships with a set of dedicated SCSS variables. Overriding them at the variable layer — before the Bootstrap source compiles — is the cleanest way to customise without specificity battles or !important hacks. This is the same philosophy covered in depth in our post on using SCSS variables to theme a Bootstrap 5 site.

    The most useful accordion variables to know:

    // _variables-override.scss
    // Place this file before @import "bootstrap"
    
    $accordion-bg:                      #ffffff;
    $accordion-border-color:            rgba(0, 0, 0, 0.1);
    $accordion-border-radius:           0.5rem;
    $accordion-inner-border-radius:     0.4rem;
    $accordion-body-padding-x:          1.5rem;
    $accordion-body-padding-y:          1.25rem;
    $accordion-button-bg:               #f8f9fa;
    $accordion-button-active-bg:        #e9ecef;
    $accordion-button-active-color:     #0d6efd;
    $accordion-button-focus-border-color: #86b7fe;
    $accordion-icon-color:              #212529;
    $accordion-icon-active-color:       #0d6efd;
    $accordion-transition:              color 0.15s ease-in-out,
                                        background-color 0.15s ease-in-out,
                                        border-color 0.15s ease-in-out,
                                        box-shadow 0.15s ease-in-out,
                                        border-radius 0.15s ease;

    If you want to replace the default chevron icon with a custom SVG (a plus/minus pattern is popular), override the icon variables using inline SVG encoded as a data URI:

    $accordion-button-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 2v12M2 8h12'/%3e%3c/svg%3e");
    
    $accordion-button-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 8h12'/%3e%3c/svg%3e");

    This technique is exactly what Canvas Template uses internally — the accordion components across its 40+ pre-built pages are all driven by SCSS variable overrides, making it trivial to reskin every accordion on the site by changing a handful of values. If you find yourself doing a lot of this kind of work, also check our guide on how to customise a Bootstrap 5 HTML template without breaking it for a systematic workflow.

    Accessibility and Performance Best Practices

    Bootstrap 5’s collapse and accordion components are designed with accessibility in mind, but there are a few things you need to get right in your own markup.

    Use semantic heading elements inside accordion headers. The .accordion-header class should wrap an actual h2h6 element appropriate to the document outline. Screen readers rely on heading hierarchy to navigate content.

    Always include aria-controls on triggers pointing to the ID of the panel they control. Bootstrap manages aria-expanded automatically, but aria-controls must be set in your HTML.

    Avoid nesting accordions deeply. While technically possible, deeply nested accordions create both accessibility challenges (complex focus management) and visual confusion. Flat structures almost always serve users better.

    For performance with many panels, be aware that all accordion content exists in the DOM from initial load. If you have panels containing heavy images or iframes, use lazy-loading attributes and only initialise third-party widgets (like carousels or maps) inside the shown.bs.collapse event callback rather than at page load. This is the same principle used in the Bootstrap 5 card components guide when dealing with media-rich card grids.

    Disable transitions on reduced-motion preference: Bootstrap respects the prefers-reduced-motion media query natively via its $enable-transitions and $enable-reduced-motion SCSS variables. If you are using the compiled CSS CDN version, you can patch this with a single CSS override:

    @media (prefers-reduced-motion: reduce) {
      .accordion-collapse {
        transition: none !important;
      }
    }

    This small addition meaningfully improves the experience for users with vestibular disorders or motion sensitivity.


    Frequently Asked Questions

    Can I use the Bootstrap 5 accordion without Bootstrap’s full CSS?

    Yes, but it requires some work. The accordion depends on CSS custom properties and a handful of utility classes (collapse, show) as well as the specific .accordion-* rules. You can extract the relevant SCSS partials (_accordion.scss and _transitions.scss) and compile only those, along with the collapse JavaScript plugin. This is viable for performance-critical projects but adds maintenance overhead when Bootstrap updates.

    How do I open a specific accordion panel on page load based on a URL hash?

    Listen to the DOMContentLoaded event, read window.location.hash, and programmatically call .show() on the matching panel using the JavaScript API. Make sure the panel’s ID matches the hash fragment, and add the show class to the first panel only as a default fallback when no hash is present.

    document.addEventListener('DOMContentLoaded', () => {
      const hash = window.location.hash;
      if (hash) {
        const targetPanel = document.querySelector(hash + '.accordion-collapse');
        if (targetPanel) {
          new bootstrap.Collapse(targetPanel, { toggle: false }).show();
        }
      }
    });

    What is the difference between data-bs-toggle="collapse" and the JavaScript API?

    Data attributes handle static, markup-driven interactions — they are processed once when the page loads. The JavaScript API gives you programmatic control at runtime, which is essential for dynamic UIs where collapse targets may be created after the initial page render, or where you need to respond to application state changes rather than user clicks.

    Why does my accordion jump or flash when animating?

    The most common cause is placing content with a fixed height or explicit padding directly inside the .collapse wrapper rather than inside an inner element. Bootstrap calculates height from the natural content height of the collapsible element, and if that element has additional CSS transitions applied, they can conflict. Always put your content inside .accordion-body (for accordions) or a plain inner div (for raw collapse), and avoid applying height, max-height, or overflow transitions to the .collapse element itself.

    Can I use Bootstrap 5 accordion inside a Bootstrap modal?

    Yes, with one caveat: the data-bs-parent attribute creates a parent-child relationship based on DOM containment. As long as your accordion wrapper is inside the modal body, it will work correctly. Just ensure your accordion and modal IDs are unique across the page, since Bootstrap uses getElementById internally. If you dynamically inject accordion markup into a modal, initialise the collapse instances manually using the JavaScript API after the shown.bs.modal event fires.


    The Bootstrap 5 accordion and collapse components are deceptively powerful. They cover the vast majority of interactive content-reveal patterns you will encounter in real projects — from simple FAQ sections to complex multi-level navigation trees — with minimal markup, no third-party dependencies, and solid accessibility out of the box. Mastering the relationship between the data-attribute API and the JavaScript API, and knowing when to reach for SCSS variables rather than overriding compiled CSS, is what separates template consumers from template masters.

    In 2026, as component-driven development continues to mature, understanding these foundational Bootstrap primitives makes you significantly faster regardless of whether you are starting from a blank file or customising a premium template. The patterns you have learned here apply equally whether you are hand-coding every line or using a tool like CanvasBuilder — the AI website builder that generates Bootstrap 5 HTML you can own and deploy anywhere — to scaffold the initial structure.

    Ready to Build Faster?

    Canvas Template gives you 40+ beautifully crafted Bootstrap 5 pages — including fully styled accordion and collapse patterns — ready to drop into any project. Stop building from scratch.

    Explore Canvas Template →
    Try CanvasBuilder AI →

  • How to Use SCSS Variables to Theme a Bootstrap 5 Site

    How to Use SCSS Variables to Theme a Bootstrap 5 Site

    How to Use SCSS Variables to Theme a Bootstrap 5 Site

    How to Use SCSS Variables to Theme a Bootstrap 5 Site

    If you’ve ever tried to restyle a Bootstrap 5 project by hunting down hardcoded hex values across dozens of CSS files, you already understand the pain this article is here to solve. SCSS variables are the clean, scalable, and professional way to theme a Bootstrap 5 site — and once you understand how Bootstrap’s Sass architecture works, you’ll wonder how you ever managed without it.

    In this guide, we’ll walk through exactly how Bootstrap 5 SCSS variables work, how to override them correctly, and how to build a consistent design system that scales from a single landing page to a full multi-section website. Whether you’re working with a custom build or a premium template like Canvas Template, these techniques apply universally.

    Key Takeaways

    • Bootstrap 5 uses a comprehensive SCSS variable system that controls colours, typography, spacing, breakpoints, and more.
    • You should never edit Bootstrap’s source files directly — always override variables in a separate file before importing Bootstrap.
    • The !default flag in Bootstrap’s SCSS means your custom values take priority when declared first.
    • CSS custom properties (variables) are generated automatically from your SCSS overrides, giving you runtime flexibility too.
    • A well-structured SCSS architecture — with a single entry point — makes theming predictable, maintainable, and team-friendly.
    • Tools like CanvasBuilder can accelerate initial setup, but understanding the SCSS layer gives you full control over the output.

    Why SCSS Variables Matter for Theming Bootstrap 5

    Bootstrap 5 was rebuilt from the ground up with a Sass-first approach. Unlike Bootstrap 3 or 4 where customisation could feel like fighting the framework, Bootstrap 5’s entire design system — from colour palettes to border radii — is exposed as overrideable SCSS variables. This means theming isn’t an afterthought; it’s a first-class feature of the framework.

    When you theme Bootstrap with SCSS, you’re not writing override rules that fight specificity wars in your CSS. You’re changing the design tokens at the source, so every component — buttons, navbars, cards, modals — inherits your changes automatically. Adjust $primary once and it propagates to hundreds of components across the entire framework.

    The alternative — writing CSS overrides after the fact — creates technical debt. You end up with brittle rules that break when Bootstrap updates, specificity nightmares, and a codebase that’s impossible to hand off to another developer. SCSS variables solve all of that elegantly.

    If you’re building out a full UI and want to see how components like cards and navbars respond to theme changes, check out our guides on Bootstrap 5 Card Components: All Variants With Live Examples and Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know — both demonstrate exactly why having a centralised SCSS variable system pays off at the component level.

    Understanding the !default Flag — The Key to Non-Destructive Overrides

    The single most important concept when working with Bootstrap’s SCSS is the !default flag. Every variable in Bootstrap’s _variables.scss file is declared with it:

    $primary: #0d6efd !default;
    $secondary: #6c757d !default;
    $success: #198754 !default;
    

    The !default flag tells Sass: “Use this value unless a value has already been assigned to this variable.” This is the mechanism that makes Bootstrap’s entire customisation system work. If you declare $primary: #e63946; in your own file before importing Bootstrap, Sass will use your value and ignore Bootstrap’s default.

    This means the rule is simple: declare your overrides before you import Bootstrap. Never touch Bootstrap’s source files — doing so makes updates a nightmare and your changes will be overwritten.

    Setting Up Your SCSS File Architecture

    A clean SCSS architecture is the foundation of a maintainable Bootstrap 5 project. Here’s the structure we recommend, and the one used in Canvas Template’s SCSS build:

    scss/
    ├── _variables.scss       ← Your Bootstrap overrides go here
    ├── _custom.scss          ← Your own component styles
    ├── _utilities.scss       ← Any utility extensions
    └── main.scss             ← Entry point — imports everything
    

    Your main.scss entry point should look like this:

    // 1. Import your variable overrides FIRST
    @import "variables";
    
    // 2. Import Bootstrap source
    @import "../node_modules/bootstrap/scss/bootstrap";
    
    // 3. Import your custom styles AFTER
    @import "custom";
    @import "utilities";
    

    This order is critical. Variables first, Bootstrap second, custom styles third. Deviating from this order is the number one cause of theming issues for developers new to the Bootstrap SCSS workflow.

    If you’re using a package manager, install Bootstrap via npm:

    npm install bootstrap@5
    

    If you’re working from a premium template like Canvas, the SCSS architecture is already scaffolded for you — you just need to locate the variables file and start overriding.

    Core Bootstrap 5 SCSS Variables You Need to Know

    Bootstrap exposes hundreds of variables, but a focused set covers the vast majority of real-world theming needs. Here’s a breakdown of the most impactful categories:

    Colour System

    // Brand colours
    $primary:   #6c63ff;
    $secondary: #f72585;
    $success:   #2dc653;
    $info:      #17a2b8;
    $warning:   #ffc107;
    $danger:    #e5383b;
    $light:     #f8f9fa;
    $dark:      #212529;
    
    // Body
    $body-bg:    #ffffff;
    $body-color: #212529;
    

    Typography

    $font-family-sans-serif: 'Inter', system-ui, -apple-system, sans-serif;
    $font-size-base:         1rem;      // 16px
    $line-height-base:       1.6;
    $font-weight-normal:     400;
    $font-weight-bold:       700;
    $headings-font-weight:   600;
    $headings-color:         #1a1a2e;
    

    Spacing

    $spacer: 1rem; // Base unit — all spacing utilities derive from this
    
    $spacers: (
      0: 0,
      1: $spacer * .25,
      2: $spacer * .5,
      3: $spacer,
      4: $spacer * 1.5,
      5: $spacer * 3,
      6: $spacer * 4,    // Custom addition
      7: $spacer * 5,    // Custom addition
    );
    

    Border Radius

    $border-radius:    0.375rem;
    $border-radius-sm: 0.25rem;
    $border-radius-lg: 0.5rem;
    $border-radius-xl: 1rem;    // Bootstrap 5.1+
    $border-radius-pill: 50rem;
    

    Shadows

    $box-shadow:    0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    $box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    $box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
    

    For a deeper dive into how typography variables interact with Bootstrap’s type scale, see our article on Bootstrap 5 Typography: Fonts, Sizes, and Hierarchy Explained.

    Building a Complete Custom Theme — A Practical Example

    Let’s build a real dark-mode-friendly theme with a purple primary brand colour. Here’s a complete _variables.scss file you could use as a starting point:

    // ============================================================
    // CANVAS CUSTOM THEME — _variables.scss
    // Override Bootstrap 5 defaults before @import "bootstrap"
    // ============================================================
    
    // Colour palette
    $primary:        #7c3aed;  // Violet
    $secondary:      #06b6d4;  // Cyan
    $success:        #10b981;
    $warning:        #f59e0b;
    $danger:         #ef4444;
    $light:          #f1f5f9;
    $dark:           #0f172a;
    
    // Body
    $body-bg:        #0f172a;
    $body-color:     #e2e8f0;
    
    // Typography
    $font-family-sans-serif: 'Inter', system-ui, sans-serif;
    $font-size-base:          1rem;
    $line-height-base:        1.7;
    $headings-font-weight:    700;
    $headings-color:          #f8fafc;
    $lead-font-size:          1.2rem;
    
    // Links
    $link-color:              $primary;
    $link-hover-color:        lighten($primary, 15%);
    $link-decoration:         none;
    
    // Buttons
    $btn-border-radius:       0.5rem;
    $btn-font-weight:         600;
    $btn-padding-y:           0.6rem;
    $btn-padding-x:           1.5rem;
    
    // Cards
    $card-bg:                 #1e293b;
    $card-border-color:       rgba(255, 255, 255, 0.08);
    $card-border-radius:      1rem;
    $card-box-shadow:         0 4px 24px rgba(0, 0, 0, 0.3);
    
    // Navbar
    $navbar-dark-color:       rgba(255, 255, 255, 0.8);
    $navbar-dark-hover-color: #ffffff;
    
    // Inputs
    $input-bg:                #1e293b;
    $input-color:             #e2e8f0;
    $input-border-color:      rgba(255, 255, 255, 0.15);
    $input-focus-border-color: $primary;
    $input-border-radius:     0.5rem;
    
    // Spacing extensions
    $spacers: (
      0: 0,
      1: 0.25rem,
      2: 0.5rem,
      3: 1rem,
      4: 1.5rem,
      5: 3rem,
      6: 5rem,
      7: 7rem,
    );
    

    This single file controls the entire visual language of your site. Every Bootstrap component will inherit these values when you compile. No specificity fights. No scattered overrides. One source of truth.

    SCSS Variables vs CSS Custom Properties — What’s the Difference?

    A common point of confusion: Bootstrap 5 generates CSS custom properties (CSS variables) from your SCSS values. So what’s the difference, and when should you use each?

    Feature SCSS Variables CSS Custom Properties
    Resolved at Compile time Runtime (in the browser)
    Can be changed after load No Yes (via JS or media queries)
    Works without a build tool No Yes
    Supports Sass functions (lighten, darken) Yes No
    Best for Full theme control, design tokens Dark mode toggling, runtime theming
    Bootstrap 5 generates these from your SCSS? Yes (source) Yes (output)

    The practical answer: use SCSS variables for all your design decisions at build time, and use the generated CSS custom properties when you need runtime flexibility — like toggling between a light and dark theme via a JavaScript class swap on <body>.

    For example, Bootstrap 5 outputs --bs-primary based on your $primary SCSS variable. You can then override it at runtime:

    /* Toggle dark theme at runtime */
    body.theme-dark {
      --bs-primary: #7c3aed;
      --bs-body-bg: #0f172a;
      --bs-body-color: #e2e8f0;
    }
    

    This gives you the best of both worlds: compile-time consistency and runtime flexibility.

    Extending the Theme With Custom Utilities and Maps

    Bootstrap 5’s utility API means you can extend the spacing, colour, and display utilities using SCSS maps without writing a single line of manual CSS. This is especially powerful when you’ve added custom colour or spacing values.

    To add your custom colours to utility classes like bg-brand or text-brand, extend Bootstrap’s colour map:

    // In _variables.scss — BEFORE importing Bootstrap
    
    // Add custom colours to Bootstrap's theme colour map
    $custom-colors: (
      "brand":   #7c3aed,
      "accent":  #06b6d4,
      "surface": #1e293b,
    );
    
    // Merge with Bootstrap's default theme colours
    $theme-colors: map-merge($theme-colors, $custom-colors);
    

    Wait — $theme-colors is defined in Bootstrap’s source, so how can you reference it before importing Bootstrap? You need to import just the functions and variables first:

    // main.scss
    
    // Step 1: Import Bootstrap functions first (required for map-merge etc.)
    @import "../node_modules/bootstrap/scss/functions";
    
    // Step 2: Import your overrides
    @import "variables";
    
    // Step 3: Import Bootstrap variables (so $theme-colors exists)
    @import "../node_modules/bootstrap/scss/variables";
    
    // Step 4: Merge your custom map additions
    $theme-colors: map-merge($theme-colors, $custom-colors);
    
    // Step 5: Import the rest of Bootstrap
    @import "../node_modules/bootstrap/scss/bootstrap";
    
    // Step 6: Your custom styles
    @import "custom";
    

    This is the advanced pattern used in production-grade builds. It’s slightly more verbose but gives you full access to Bootstrap’s internal maps while still injecting your own values cleanly. Canvas Template’s SCSS build follows exactly this pattern, which is why theming it is as simple as editing a single variables file.

    For those building complete sites from scratch and wanting a head start, How to Customise a Bootstrap 5 HTML Template Without Breaking It covers the broader workflow of working within an existing template structure.

    Common SCSS Theming Mistakes and How to Avoid Them

    Even experienced developers trip up in predictable ways when theming Bootstrap with SCSS. Here are the most common pitfalls and how to avoid them:

    1. Editing Bootstrap’s source files directly. This is the most destructive mistake. When you run npm update bootstrap, all your changes vanish. Always work in your own files.

    2. Declaring overrides after importing Bootstrap. The !default flag means Bootstrap won’t pick up variables declared after the import. Order matters absolutely.

    3. Using CSS hex values where SCSS colour functions would work. Instead of guessing a lighter shade of your primary, use lighten($primary, 10%) or Bootstrap’s own tint-color() and shade-color() functions for perceptually consistent results.

    // Bootstrap 5's built-in colour utilities
    $primary-light: tint-color($primary, 40%);   // Mix with white
    $primary-dark:  shade-color($primary, 20%);  // Mix with black
    

    4. Not testing across all components. A colour change to $primary affects buttons, badges, alerts, links, focus rings, and more. Always do a full UI review after theme changes, not just a spot check.

    5. Forgetting to update the colour contrast. Bootstrap 5 includes a contrast-checking mechanism. If your $primary is too dark or light, white text on a primary button may fail WCAG contrast requirements. Use Bootstrap’s color-contrast() function or test with a contrast checker.

    If you’re building a landing page and want to see all of this in practice on a real layout, our guide on How to Build a Landing Page with Bootstrap 5 in Under 1 Hour walks through component assembly with theming in mind from the start.


    Frequently Asked Questions

    Do I need Node.js and npm to use Bootstrap 5 SCSS variables?

    Yes, in most practical workflows you’ll need a Sass compiler, which is typically set up via Node.js using a build tool like Vite, Webpack, or Parcel. You install Bootstrap via npm and import its SCSS source. That said, some tools like CodeKit or Scout-App provide a GUI-based Sass compiler if you prefer to avoid the command line. You cannot use SCSS variable overrides with Bootstrap’s pre-compiled CSS CDN version — that only supports CSS custom property overrides at runtime.

    What’s the difference between $primary and --bs-primary in Bootstrap 5?

    $primary is a Sass variable resolved at compile time — it’s used to generate all the component styles during your build process. --bs-primary is a CSS custom property that Bootstrap automatically outputs based on your $primary value. The CSS variable can be changed at runtime (for example, for dark mode toggling), but it won’t retroactively update compiled styles. Use SCSS variables for reliable theming; use the CSS variables when you need JavaScript-driven or media-query-driven runtime changes.

    Can I add completely new colour variants to Bootstrap’s theme system?

    Absolutely. By using map-merge() to extend Bootstrap’s $theme-colors map, you can add custom named colours like brand, accent, or surface. Once added, Bootstrap will automatically generate utility classes like .bg-brand, .text-brand, .btn-brand, and .badge bg-brand for your new colour — exactly as it does for the built-in theme colours. This is one of the most powerful features of Bootstrap 5’s Sass architecture.

    How do SCSS variable overrides work in a premium Bootstrap template like Canvas?

    Canvas Template ships with a dedicated _variables.scss file where all Bootstrap overrides are pre-organised. To retheme the template, you simply edit that file — changing $primary, $font-family-sans-serif, or any other variable — recompile, and the entire template updates. Because Canvas follows the correct import order (your variables before Bootstrap’s source), changes propagate instantly to all components. You don’t need to hunt through component files or write CSS overrides.

    Should I use SCSS maps or individual variable overrides for custom spacing?

    Both approaches work, but extending Bootstrap’s $spacers map is cleaner for spacing because Bootstrap’s utility generation is map-driven. If you add entries to $spacers, Bootstrap automatically generates .p-6, .mt-7, .gap-6, and all the corresponding utility classes. Individual variable overrides (like changing $spacer from 1rem to 1.125rem) affect the base unit and scale everything proportionally, which is useful for tighter or more airy designs. In practice, most projects do both: adjust the base $spacer and extend the map with additional steps.


    Start Building Smarter With SCSS and Bootstrap 5

    Mastering Bootstrap 5 SCSS variables transforms how you build websites. Instead of wrestling with CSS specificity and scattered overrides, you’re working with a coherent design system that scales from a single component to an entire product. Every minute invested in setting up your SCSS architecture correctly saves hours of debugging and refactoring later.

    If you want to skip the scaffolding and start with a professionally structured Bootstrap 5 SCSS codebase out of the box, Canvas Template is built exactly this way. The SCSS architecture follows everything covered in this guide — correct import order, a pre-populated variables file, and component styles that inherit theme changes automatically. It’s available on ThemeForest and used by thousands of developers and agencies worldwide.

    If you’d rather generate a themed Bootstrap 5 site quickly without touching code at all, CanvasBuilder — the AI-powered website builder — handles layout and initial styling through a visual interface, giving you a clean starting point you can then take further with your own SCSS customisations.

    Explore Canvas Template on ThemeForest →
    Build with CanvasBuilder AI →

  • Bootstrap 5 Card Components: All Variants With Live Examples

    Bootstrap 5 Card Components: All Variants With Live Examples

    Bootstrap 5 Card Components: All Variants With Live Examples

    Bootstrap 5 Card Components: All Variants With Live Examples

    The Bootstrap 5 card component is one of the most versatile building blocks in the entire framework. Whether you are building a blog grid, a pricing table, a product listing, a team member section, or a dashboard widget, cards are almost certainly going to be part of your layout. Yet most developers only scratch the surface of what Bootstrap card variants can actually do.

    In this guide we are going to walk through every major Bootstrap card variant — from the basic structure right through to horizontal cards, card groups, overlay cards, and custom styled cards — with real code examples you can copy directly into your project. By the end you will have a solid mental model of the full card API and know exactly which variant to reach for in any given design situation.

    If you are still getting comfortable with Bootstrap 5 layouts in general, it is worth reading our Bootstrap 5 Grid System: The Complete Guide for 2026 first, since cards almost always live inside a grid wrapper. Once you have cards mastered, layering them into a full landing page is straightforward — our guide on How to Build a Landing Page with Bootstrap 5 in Under 1 Hour shows exactly how that works end to end.

    Key Takeaways

    • Bootstrap 5 cards are built from a small set of inner elements: .card-body, .card-header, .card-footer, .card-img-top, and .card-title / .card-text.
    • You can create image overlays, horizontal cards, card groups, and card grids purely with Bootstrap utility classes — no custom CSS required.
    • Colour variants use background and border utilities (bg-primary, border-danger, etc.) rather than dedicated card modifier classes.
    • Card height equalisation across columns is handled with Flexbox — use h-100 on the .card inside a .row-cols-* wrapper.
    • For production projects, a premium template like Canvas already ships polished card patterns so you do not have to build every variant from scratch.

    The Anatomy of a Bootstrap 5 Card

    Before jumping into variants, you need to be fluent with the core building blocks. A Bootstrap 5 card is a <div> with the class .card. Everything inside is optional but follows a predictable structure.

    <div class="card" style="width: 20rem;">
      <img src="thumbnail.jpg" class="card-img-top" alt="Card image">
      <div class="card-body">
        <h5 class="card-title">Card Title</h5>
        <p class="card-text">A short description that supports the card title and gives context.</p>
        <a href="#" class="btn btn-primary">Go somewhere</a>
      </div>
    </div>
    

    The key classes and their roles:

    • .card — The outer wrapper. Sets position: relative, a white background, a subtle border, and border-radius.
    • .card-body — The primary padded content area. This is where your text, buttons, and form elements live.
    • .card-title — Applied to an <h5> (or any heading level). Adds bottom margin.
    • .card-subtitle — Applied to an <h6>. Uses muted text and negative top margin to sit close to the title.
    • .card-text — Applied to a <p>. Removes bottom margin on the last child.
    • .card-link — Applied to an <a> tag inside .card-body. Adds spacing between adjacent links.
    • .card-img-top / .card-img-bottom — Rounds the top or bottom corners of an image to match the card border.
    • .card-header / .card-footer — Grey-tinted top and bottom sections with their own padding and border.

    Adding a header and footer gives your card a more structured, dashboard-style appearance. Headers and footers are particularly useful for pricing cards, feature boxes, and data widgets.

    <div class="card">
      <div class="card-header">
        Featured
      </div>
      <div class="card-body">
        <h5 class="card-title">Special offer</h5>
        <p class="card-text">
          With supporting text below as a natural lead-in to additional content.
        </p>
        <a href="#" class="btn btn-primary">Go somewhere</a>
      </div>
      <div class="card-footer text-muted">
        2 days ago
      </div>
    </div>
    

    You can also place a <blockquote> with the .blockquote class inside a card body for quote-style cards — a common pattern for testimonials. The footer then becomes a natural .blockquote-footer attribution area. For more on Bootstrap typographic patterns that complement this approach, see our Bootstrap 5 Typography: Fonts, Sizes, and Hierarchy Explained guide.

    Bootstrap Card Colour Variants

    Bootstrap 5 removed dedicated colour modifier classes for cards (like the old .card-primary from Bootstrap 3/4). Instead, you apply standard background and text utilities directly. This is more flexible and consistent with the rest of the utility-first design in Bootstrap 5.

    <div class="card text-bg-primary mb-3">
      <div class="card-header">Primary</div>
      <div class="card-body">
        <h5 class="card-title">Primary card title</h5>
        <p class="card-text">Some quick example text.</p>
      </div>
    </div>
    
    <div class="card text-bg-success mb-3">
      <div class="card-header">Success</div>
      <div class="card-body">
        <h5 class="card-title">Success card title</h5>
        <p class="card-text">Some quick example text.</p>
      </div>
    </div>
    
    <div class="card text-bg-danger mb-3">
      <div class="card-header">Danger</div>
      <div class="card-body">
        <h5 class="card-title">Danger card title</h5>
        <p class="card-text">Some quick example text.</p>
      </div>
    </div>
    

    The text-bg-* helper (introduced in Bootstrap 5.2) automatically sets both the background colour and a contrasting text colour in a single class. For outline-style coloured cards, use border-* on the outer .card and text-* on the .card-header:

    <div class="card border-warning mb-3">
      <div class="card-header text-warning">Warning</div>
      <div class="card-body">
        <h5 class="card-title">Warning card title</h5>
        <p class="card-text text-muted">Some quick example text.</p>
      </div>
    </div>
    

    Image Overlay Cards

    An image overlay card places the card body on top of an image rather than below it. This is a popular pattern for hero-style blog post cards, portfolio thumbnails, and event listings.

    <div class="card text-bg-dark">
      <img src="bg-image.jpg" class="card-img" alt="Background image">
      <div class="card-img-overlay">
        <h5 class="card-title">Card title</h5>
        <p class="card-text">
          This is a wider card with supporting text below as a natural lead-in.
        </p>
        <p class="card-text"><small>Last updated 3 mins ago</small></p>
      </div>
    </div>
    

    Note the difference: .card-img (instead of .card-img-top) makes the image fill the full card area including corners. The .card-img-overlay is then absolutely positioned over it. For legibility, combine this with text-bg-dark or add a CSS gradient overlay on the image itself.

    Horizontal Cards

    Horizontal cards combine the Bootstrap grid with the card component. They are ideal for list-style layouts — search results, article previews, and product rows where an image sits beside the content rather than above it.

    <div class="card mb-3">
      <div class="row g-0">
        <div class="col-md-4">
          <img src="thumbnail.jpg" class="img-fluid rounded-start" alt="Card image">
        </div>
        <div class="col-md-8">
          <div class="card-body">
            <h5 class="card-title">Card title</h5>
            <p class="card-text">
              This is a wider card with supporting text below as a natural lead-in to additional content.
            </p>
            <p class="card-text">
              <small class="text-muted">Last updated 3 mins ago</small>
            </p>
          </div>
        </div>
      </div>
    </div>
    

    The class .g-0 removes the gutter so the image sits flush against the card edge. .rounded-start rounds only the left corners of the image, matching the card’s own rounded corners on the left side.

    Card Groups, Grids, and Equal Height

    One of the most common pain points when using cards in a multi-column layout is unequal card heights when the content length varies. Bootstrap 5 solves this neatly.

    Card Groups

    A .card-group renders cards as a single attached unit with no gap between them and equal heights via Flexbox:

    <div class="card-group">
      <div class="card">
        <img src="img1.jpg" class="card-img-top" alt="...">
        <div class="card-body">
          <h5 class="card-title">Card title</h5>
          <p class="card-text">Short description.</p>
        </div>
      </div>
      <div class="card">
        <img src="img2.jpg" class="card-img-top" alt="...">
        <div class="card-body">
          <h5 class="card-title">Card title</h5>
          <p class="card-text">A much longer description that takes up considerably more vertical space than the others around it.</p>
        </div>
      </div>
      <div class="card">
        <img src="img3.jpg" class="card-img-top" alt="...">
        <div class="card-body">
          <h5 class="card-title">Card title</h5>
          <p class="card-text">Short description.</p>
        </div>
      </div>
    </div>
    

    Grid Cards With Equal Height

    For a gapped grid with independent cards (not merged), use .row-cols-* with h-100 on the card:

    <div class="row row-cols-1 row-cols-md-3 g-4">
      <div class="col">
        <div class="card h-100">
          <img src="img1.jpg" class="card-img-top" alt="...">
          <div class="card-body">
            <h5 class="card-title">Card title</h5>
            <p class="card-text">Short body text.</p>
          </div>
          <div class="card-footer">
            <small class="text-muted">Last updated 3 mins ago</small>
          </div>
        </div>
      </div>
      <div class="col">
        <div class="card h-100">
          <img src="img2.jpg" class="card-img-top" alt="...">
          <div class="card-body">
            <h5 class="card-title">Card title</h5>
            <p class="card-text">A longer body text that pushes the footer further down.</p>
          </div>
          <div class="card-footer">
            <small class="text-muted">Last updated 3 mins ago</small>
          </div>
        </div>
      </div>
    </div>
    

    The h-100 class makes each card stretch to fill its column, which in a Flexbox row means all cards in the same row become equal height. The g-4 adds a consistent gap between columns and rows.

    Bootstrap Card Variants: Quick Comparison

    Here is a reference table comparing the main Bootstrap card variants by use case, key classes, and behaviour:

    Variant Key Classes Best Use Case Equal Height?
    Basic Card .card, .card-body Single content block N/A
    Card with Header/Footer .card-header, .card-footer Pricing, dashboards, blog post previews Via h-100
    Colour Variant text-bg-*, border-* Status indicators, alerts, highlight boxes Via h-100
    Image Overlay .card-img, .card-img-overlay Hero-style blog cards, portfolio thumbnails N/A (fixed height recommended)
    Horizontal Card .row.g-0, .rounded-start Search results, article lists, product rows Auto via Flexbox
    Card Group .card-group Comparison sections, merged layouts Yes, automatic
    Card Grid .row-cols-*, .g-*, .h-100 Blog grids, product catalogues, team sections Yes, via h-100
    List Group Card .list-group-flush inside .card Feature lists, navigation menus inside cards Via h-100

    Advanced Card Customisation and Real-World Patterns

    Beyond the core variants, there are several patterns you will encounter repeatedly in real projects. Here are the most important ones.

    List Group Inside a Card

    Placing a .list-group-flush inside a card removes the outer borders from the list group so it flows seamlessly within the card boundaries:

    <div class="card" style="width: 18rem;">
      <div class="card-header">Featured</div>
      <ul class="list-group list-group-flush">
        <li class="list-group-item">Feature one</li>
        <li class="list-group-item">Feature two</li>
        <li class="list-group-item">Feature three</li>
      </ul>
      <div class="card-body">
        <a href="#" class="btn btn-primary w-100">Get started</a>
      </div>
    </div>
    

    Making an Entire Card Clickable

    A popular UX pattern is a fully clickable card. The cleanest Bootstrap 5 approach uses the .stretched-link utility on the anchor inside the card body, which stretches its pseudo-element to cover the entire card:

    <div class="card">
      <img src="thumbnail.jpg" class="card-img-top" alt="...">
      <div class="card-body">
        <h5 class="card-title">Clickable card</h5>
        <p class="card-text">Click anywhere on this card.</p>
        <a href="#" class="btn btn-primary stretched-link">Read more</a>
      </div>
    </div>
    

    Custom Hover Effect

    Bootstrap does not ship a hover state for cards, but adding one is trivial with a small CSS block:

    .card {
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }
    
    .card:hover {
      transform: translateY(-4px);
      box-shadow: 0 0.75rem 1.5rem rgba(0, 0, 0, 0.12);
    }
    

    This gives a subtle lift effect on hover that works beautifully for blog grids and portfolio layouts. In How to Customise a Bootstrap 5 HTML Template Without Breaking It, we go deeper into how to safely extend Bootstrap’s component defaults without overriding the source files.

    If you are working with a premium template like Canvas Template, many of these patterns are already pre-built as component variants — the card hover effects, image overlays, and equal-height grids come included and are customisable via Sass variables and utility overrides. That alone saves hours on a typical agency project. If you would rather skip the code entirely, CanvasBuilder — the AI website builder built on top of Canvas — lets you compose card-based sections visually, which is particularly useful for prototyping before you hand off to a developer.

    Card With Navigation Tabs

    For tabbed card content — common in dashboards and documentation — place a .nav.nav-tabs inside the .card-header:

    <div class="card">
      <div class="card-header">
        <ul class="nav nav-tabs card-header-tabs">
          <li class="nav-item">
            <a class="nav-link active" aria-current="true" href="#">Active</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#">Link</a>
          </li>
          <li class="nav-item">
            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
          </li>
        </ul>
      </div>
      <div class="card-body">
        <h5 class="card-title">Special title treatment</h5>
        <p class="card-text">Tab content goes here.</p>
        <a href="#" class="btn btn-primary">Go somewhere</a>
      </div>
    </div>
    

    The .card-header-tabs class removes the bottom border from the tab strip and aligns it flush with the card header, creating a seamless appearance.

    Cards rarely live in isolation. In real projects they sit inside section containers, are filtered by JavaScript, or are dynamically rendered from a CMS or API. A few layout principles to keep in mind:

    • Always wrap card grids in a .container or .container-fluid to respect your page’s horizontal rhythm. Pairing this with the responsive column classes (row-cols-1 row-cols-sm-2 row-cols-lg-3) gives you a mobile-first cascade with zero media query custom code.
    • Use gap utilities (g-3, g-4) rather than manual margin classes on cards. The gutter system in Bootstrap 5 distributes space evenly and avoids the “extra margin on the last column” problem that plagued Bootstrap 3 and 4.
    • Pair cards with your navbar structure. If you are building a site section by section, our Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know guide covers how to make the navigation sit cleanly above card-heavy content sections.
    • Test card layouts at every breakpoint. The most common breakage points are at sm (576px) and lg (992px) — use browser DevTools responsive mode during development.

    Frequently Asked Questions

    What is the difference between .card-group and a row with .row-cols-* for card layouts?

    .card-group merges cards into a single unit by removing the gap between them and collapsing their individual borders into shared borders. It also equalises height automatically. A .row with .row-cols-* keeps cards as independent elements with gutters between them. Use .card-group when you want a connected, comparison-style layout; use the row/column approach when you want visually separate cards in a grid.

    How do I make all cards in a row the same height in Bootstrap 5?

    Add h-100 to the .card element inside each column. Because each .col in a Bootstrap row is a Flexbox child, h-100 stretches the card to fill its column height. The row sets all columns to equal height, so all cards end up uniform. If you also want the footer pinned to the bottom, use d-flex flex-column on the card and mt-auto on the .card-footer.

    Can I use Bootstrap 5 cards without jQuery?

    Yes. Bootstrap 5 dropped the jQuery dependency entirely. All Bootstrap 5 JavaScript (dropdowns, modals, tabs) is written in vanilla ES6. Cards themselves are pure CSS components with no JavaScript dependency at all, so they work in any project regardless of your JavaScript stack.

    How do I add a hover effect to a Bootstrap 5 card?

    Bootstrap does not ship a built-in hover state for cards. Add your own with a small CSS rule: set transition on .card and then define a .card:hover rule with transform: translateY(-4px) and an elevated box-shadow. This gives a clean “lift” effect. For production sites, a premium template like Canvas includes polished hover variants out of the box.

    What replaced .card-columns in Bootstrap 5?

    Bootstrap 4 had .card-columns which used CSS Multi-column layout to create a masonry-style card layout. This was removed in Bootstrap 5 because it had awkward ordering behaviour (columns filled top-to-bottom, not left-to-right). The recommended Bootstrap 5 replacement is either the CSS Grid masonry spec (still experimental in browsers as of 2026) or a lightweight JavaScript masonry library like Masonry.js paired with Bootstrap’s grid classes.


    Build Faster With Canvas Template

    If you are building production sites with Bootstrap 5, starting from a well-architected template saves days of component work. Canvas Template — the #1 premium Bootstrap 5 HTML template on ThemeForest — ships with dozens of pre-built card patterns, section layouts, and UI components all following the same conventions covered in this guide.

    Want to skip the code entirely and prototype visually? CanvasBuilder is the AI-powered website builder built on top of Canvas. Compose card-based sections, landing pages, and full sites without writing a line of Bootstrap HTML manually.

    Explore Canvas Template →
    Try CanvasBuilder Free →

  • HTML Template vs WordPress Theme: Which Should You Choose in 2026?

    HTML Template vs WordPress Theme: Which Should You Choose in 2026?

    HTML Template vs WordPress Theme: Which Should You Choose in 2026?

    HTML Template vs WordPress Theme: Which Should You Choose in 2026?

    Every web project starts with the same question: what foundation do I build on? In 2026, that question has become more nuanced than ever. The choice between a static HTML template and a WordPress theme is no longer simply about technical preference — it shapes your performance, your maintenance overhead, your client handoffs, and ultimately your bottom line.

    Whether you are a freelancer quoting a new project, an agency standardising your tech stack, or a developer building your own product, this guide breaks down the HTML template vs WordPress theme decision with real code, real tradeoffs, and practical recommendations you can act on today.

    Key Takeaways

    • HTML templates deliver faster load times and lower hosting costs — ideal for performance-critical projects.
    • WordPress themes provide a content management layer that non-technical clients can use independently.
    • Bootstrap 5 HTML templates like Canvas Template give developers full control over markup, styles, and scripts without plugin conflicts.
    • WordPress carries ongoing maintenance costs — updates, plugin compatibility, and security patches add up over time.
    • Static HTML is not “basic” — modern Bootstrap 5 templates are feature-rich, responsive, and highly customisable.
    • The right choice depends on three factors: who manages content, how performance-critical the site is, and how complex the feature set needs to be.
    • AI website builders like CanvasBuilder.co are closing the gap for non-developers who want static-HTML speed without hand-coding.

    Understanding the Core Difference

    Before picking a side, it helps to understand exactly what each option is — and is not.

    A static HTML template is a pre-built set of HTML, CSS, and JavaScript files. When a browser requests a page, the server returns the file directly. There is no database query, no PHP execution, no plugin stack firing in sequence. A Bootstrap 5 template like Canvas Template ships with dozens of pre-built sections, UI components, and page layouts — all of which you wire together with clean, standards-compliant HTML.

    A WordPress theme is a presentation layer that sits on top of the WordPress CMS. The theme controls how content is displayed, but content itself lives in a MySQL database. When a visitor hits a page, WordPress bootstraps PHP, queries the database, merges theme templates with content, and renders HTML on the fly. That dynamic pipeline is what makes WordPress so flexible for content editors — and what makes it measurably slower and more complex to maintain.

    Neither is universally better. They solve different problems. The key is knowing which problem your project actually has.

    Performance and Hosting Costs in 2026

    Performance is no longer just a nice-to-have. Core Web Vitals remain a Google ranking factor in 2026, and users abandon pages that take more than three seconds to load. This is where static HTML wins decisively.

    A basic static HTML page served from a CDN can achieve a Time to First Byte (TTFB) under 50ms. A comparable WordPress page — even with caching plugins, an optimised theme, and a managed host — typically ranges between 200ms and 600ms TTFB. That gap matters at scale.

    Factor Static HTML Template WordPress Theme
    Average TTFB 30–80ms (CDN) 200–600ms (optimised)
    Hosting cost (entry level) $0–$5/month (static host/CDN) $10–$30/month (PHP/MySQL host)
    Server requirements Any web server or CDN PHP 8.x + MySQL/MariaDB
    Security attack surface Very low High (WP core, plugins, themes)
    Core Web Vitals baseline Excellent Good (with optimisation)
    Scalability under traffic Near-unlimited via CDN Requires scaling plan

    For agencies building landing pages, portfolio sites, or product showcase pages, the static HTML route removes an entire tier of infrastructure complexity. You can deploy a Canvas Template project to Netlify, Vercel, or Cloudflare Pages for free — and serve it globally with CDN edge caching out of the box.

    Developer Experience and Customisation

    For front-end developers, working directly with HTML, CSS, and JavaScript is the path of least resistance. There are no PHP template hierarchies to understand, no plugin conflicts to debug, and no WP-admin rabbit holes to fall into. You open a file, write code, and see the result.

    A typical Bootstrap 5 HTML template structure looks like this:

    <!-- Canvas Template: Hero section with responsive grid -->
    <section class="py-5 bg-light">
      <div class="container">
        <div class="row align-items-center g-4">
          <div class="col-lg-6">
            <h1 class="display-4 fw-bold lh-sm mb-4">
              Build faster. <br>Ship smarter.
            </h1>
            <p class="lead text-muted mb-4">
              The Bootstrap 5 template built for developers
              who care about performance and design quality.
            </p>
            <a href="#" class="btn btn-primary btn-lg px-5">
              Get Started
            </a>
          </div>
          <div class="col-lg-6">
            <img src="assets/images/hero.png" 
                 class="img-fluid rounded-4 shadow-lg" 
                 alt="Dashboard preview">
          </div>
        </div>
      </div>
    </section>

    That code is portable, readable, and entirely under your control. If you want to customise Bootstrap’s default variables, you do it in SCSS and recompile — no filter hooks, no child theme gymnastics:

    // _variables.scss — Override Bootstrap defaults before importing
    $primary:         #5c6ef8;
    $body-font-size:  1rem;
    $border-radius:   0.5rem;
    $font-family-sans-serif: 'Inter', system-ui, -apple-system, sans-serif;
    
    // Then import Bootstrap source
    @import "bootstrap/scss/bootstrap";

    If you have not yet explored how deep Bootstrap 5 customisation can go, our guide on how to customise a Bootstrap 5 HTML template without breaking it is worth bookmarking — it covers the exact approach to making sweeping design changes without touching vendor files.

    With WordPress, customisation is layered through PHP template parts, hooks, and block editor patterns. That is not inherently bad — for CMS-dependent projects it is actually the right model — but for front-end-heavy work it adds cognitive overhead that slows development.

    Content Management: The Case for WordPress

    WordPress wins one category convincingly: content management by non-developers.

    If a client needs to publish three blog posts a week, update product listings, manage a team page, or run an e-commerce store — WordPress and its ecosystem (WooCommerce, ACF, Elementor) provide a GUI-driven workflow that non-technical users can operate without developer support.

    This is a real and important requirement. Handing a static HTML project to a client who then needs to phone you every time they want to update their “About Us” paragraph is not a viable long-term arrangement. WordPress solves that. For content-heavy sites, a well-built WordPress theme on top of a fast managed host (Kinsta, WP Engine, Cloudways) is a legitimate professional choice.

    That said, the content management gap is narrowing. Headless CMS options (Contentful, Sanity, Decap CMS) now plug directly into static HTML projects. You can give clients a clean editing interface and still serve fully static files from a CDN. This hybrid approach is gaining traction in 2026 and is worth evaluating for mid-complexity projects.

    Security and Maintenance Overhead

    This is the category that WordPress proponents often underestimate — and where static HTML quietly wins over multi-year project lifecycles.

    WordPress powers roughly 43% of the web in 2026, which makes it the most targeted CMS on the planet. A default WordPress installation has dozens of attack vectors: XML-RPC endpoints, REST API exposure, login brute force, outdated plugins, and theme vulnerabilities. Responsible WordPress ownership means:

    • Updating WordPress core, plugins, and themes — often weekly
    • Running a security plugin (Wordfence, Sucuri) and paying for firewall rules
    • Monitoring for plugin conflicts after each update cycle
    • Maintaining PHP version compatibility as hosts deprecate older versions
    • Managing database backups, file backups, and restore testing

    A static HTML site has none of those concerns. There is no database to compromise, no PHP runtime to patch, and no plugin to go abandoned by its author. The only maintenance is renewing your domain and keeping your CDN or hosting account active.

    For agencies managing dozens of client sites, this maintenance delta has real cost. Even at 30 minutes per WordPress site per month, a portfolio of 40 sites is 20 hours of work — work that a static HTML portfolio simply does not generate.

    When to Choose a Bootstrap HTML Template

    Choose a static Bootstrap 5 HTML template when:

    • Performance is a hard requirement. Landing pages, product launches, SaaS marketing sites, and portfolio pages all benefit from sub-100ms load times. Static HTML is the fastest path to excellent Core Web Vitals scores.
    • The content is relatively stable. If the site gets updated a handful of times per quarter rather than daily, the lack of a CMS is not a practical limitation.
    • The developer owns the output. Agencies and freelancers who manage all content changes on behalf of clients retain full control — and avoid the support overhead of training clients on WordPress admin.
    • You need a clean, lean codebase. No WordPress means no 300KB of jQuery legacy, no Gutenberg scripts loading on every page, no 40-plugin dependency chain.
    • Budget is tight on hosting. A Bootstrap template project can be hosted for free or near-free on platforms like GitHub Pages, Cloudflare Pages, or Netlify — removing the monthly managed WordPress hosting fee entirely.

    If you are building a landing page right now, our practical walkthrough on how to build a landing page with Bootstrap 5 in under one hour gives you a production-ready workflow using exactly this approach. Pair it with our deep dive on Bootstrap 5 typography to nail the type hierarchy from the start.

    When to Choose a WordPress Theme

    Choose a WordPress theme when:

    • Content volume is high and ongoing. News sites, blogs with multiple contributors, and e-commerce catalogues genuinely need a CMS. WordPress handles this well.
    • Clients demand self-service editing. If the project handoff means the client takes over all content updates independently, WordPress admin is the practical choice — especially if they are already familiar with it.
    • Complex functionality is required out of the box. Membership sites, job boards, booking systems, and online stores have mature WordPress plugin ecosystems. Replicating that in static HTML requires custom development or third-party SaaS integrations.
    • The client explicitly requests WordPress. Sometimes the answer is dictated by existing infrastructure — the client already has a WordPress host, an existing plugin licence, or an in-house WordPress developer.

    If you are evaluating specific WordPress themes for a project, our comparison of Canvas Template vs Flatsome vs Avada breaks down how the leading ThemeForest options stack up against each other in 2026.

    The AI Builder Wildcard in 2026

    No honest comparison of HTML templates versus WordPress themes in 2026 is complete without addressing the AI website builder layer that is reshaping the entire conversation.

    Tools like CanvasBuilder.co — which is built on top of the Canvas Template HTML framework — let you generate a fully structured, Bootstrap 5 HTML website from a text prompt. The output is clean static HTML that you can deploy immediately or hand to a developer for customisation. It bridges the gap between “I need a fast static site” and “I do not have time to build it from scratch.”

    This is significant. One of WordPress’s strongest selling points has always been that non-developers can publish and manage content without touching code. AI HTML builders are now offering a comparable on-ramp for static HTML — you describe what you want, get a production-quality starting point, and ship it to a CDN without touching a PHP server.

    For agencies, this also changes the project economics. A landing page that previously took four hours to build from a template can now be roughed out in minutes, leaving the developer budget for the high-value customisation work that actually requires expertise.


    Frequently Asked Questions

    Is a static HTML template suitable for SEO in 2026?

    Yes — in fact, static HTML often outperforms WordPress for SEO because of faster load times and cleaner markup. Core Web Vitals (LCP, CLS, FID/INP) are ranking signals, and static sites consistently score higher. You can add structured data, Open Graph tags, and canonical URLs directly in your HTML without needing an SEO plugin. The only thing to handle manually is sitemap generation, which tools like xml-sitemaps.com or a simple CI script can automate.

    Can I build a blog with an HTML template?

    You can, though it requires a different workflow than WordPress. Static site generators like Eleventy, Hugo, or Astro let you author content in Markdown and compile it into a static HTML site at build time. You can skin these generators with a Bootstrap 5 HTML template and get a fully static blog with near-zero hosting costs. The tradeoff is that publishing new posts requires a build step rather than a WordPress publish button — acceptable for developer-managed blogs, but potentially awkward for non-technical content teams.

    Which is cheaper long-term — HTML template or WordPress theme?

    Static HTML is almost always cheaper over a multi-year horizon. A premium Bootstrap 5 template like Canvas Template is a one-time purchase. Hosting is free or near-free on static platforms. There are no recurring plugin licence fees, no managed WordPress hosting bills ($25–$80/month on quality hosts), and no security plugin subscriptions. For a three-year project, a well-run WordPress setup can cost three to five times more in total infrastructure and maintenance than an equivalent static HTML deployment.

    Do Bootstrap HTML templates work well on mobile?

    Absolutely. Bootstrap 5 is mobile-first by design. Every layout built with the Bootstrap grid system is responsive out of the box. If you want to see exactly how the grid system handles breakpoints, our Bootstrap 5 grid system guide for 2026 covers every responsive utility class and layout pattern in detail. Additionally, Bootstrap 5’s navbar component — a frequent sticking point on mobile — has eight common patterns documented in our Bootstrap 5 navbar customisation guide.

    What if I start with HTML and later need a CMS?

    This is a common evolution path, and there are clean ways to handle it. A headless CMS (Contentful, Sanity, Netlify CMS) can front your static HTML files without requiring a migration to WordPress. Alternatively, a Jamstack architecture — where a static site generator pulls data from a headless CMS at build time — gives you a CMS editing interface while keeping your output fully static. If the project eventually outgrows that model, migrating to WordPress at a later stage is always an option, but many teams find the headless approach handles their needs indefinitely.


    Ready to Build Faster in 2026?

    If speed, control, and clean code matter to your next project, a premium Bootstrap 5 HTML template gives you the foundation to build better sites — without the WordPress complexity. Explore the full Canvas Template library at canvastemplate.com and see why thousands of developers and agencies choose it for client work, product sites, and personal projects alike.

    Want to go from brief to live site even faster? Try CanvasBuilder.co — the AI website builder powered by Canvas Template — and generate a production-ready Bootstrap 5 HTML site from a single prompt.

  • Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know

    Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know

    Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know

    Bootstrap 5 Navbar: 8 Customisation Patterns You Need to Know

    The navbar is the first thing a visitor interacts with on your website. It sets the tone for your entire UI, guides navigation, and — when done well — disappears into the background and just works. When done poorly, it frustrates users, breaks on mobile, and undermines the credibility of your entire project.

    Bootstrap 5’s navbar component is one of the most powerful and flexible pieces of the framework. It ships with responsive collapse, multiple colour schemes, support for dropdowns, forms, and utility-based spacing — all out of the box. But most developers only scratch the surface of what’s possible.

    In this tutorial, we’ll walk through eight practical customisation patterns for the Bootstrap 5 navbar — from sticky behaviour and transparent overlays to mega menus and dark-mode-aware navbars. Each pattern includes real code you can copy and adapt immediately. Whether you’re building from scratch or working inside a premium template like Canvas Template, these techniques will level up your navigation game considerably.

    If you’re newer to Bootstrap 5 layouts in general, it’s worth reading our guide on Bootstrap 5 Grid System: The Complete Guide for 2026 before diving in — a solid understanding of the grid will help you control navbar layouts with more precision.

    Key Takeaways

    • Bootstrap 5’s navbar component is fully responsive and highly customisable without requiring JavaScript from scratch.
    • Sticky, fixed, and transparent navbars each serve different UX purposes — choosing the right one matters.
    • You can implement mega menus using Bootstrap’s dropdown and grid system together.
    • Dark mode-aware navbars are achievable with CSS custom properties and Bootstrap’s colour mode utilities.
    • Custom CSS variables (not just utility classes) give you the deepest level of navbar control.
    • Accessibility should always be considered — ARIA attributes and keyboard navigation are non-negotiable.

    Understanding the Bootstrap 5 Navbar Anatomy

    Before customising anything, you need to understand the structural anatomy of a Bootstrap 5 navbar. The component is built around a handful of key classes that each play a specific role.

    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">MyBrand</a>
        <button class="navbar-toggler" type="button"
                data-bs-toggle="collapse"
                data-bs-target="#mainNav"
                aria-controls="mainNav"
                aria-expanded="false"
                aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="mainNav">
          <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" href="#">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#">About</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#">Contact</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>

    The key classes to understand: navbar-expand-{breakpoint} controls at which viewport size the nav collapses. navbar-light or navbar-dark sets the icon and text colour scheme. bg-* utilities handle background colour. ms-auto on the ul pushes navigation items to the right. These are your levers — the customisation patterns below all work by adjusting these and adding targeted overrides.

    Pattern 1 — Sticky vs Fixed Navbar (and When to Use Each)

    One of the most common navbar requirements is keeping it visible as the user scrolls. Bootstrap 5 gives you two distinct approaches, and they behave very differently.

    Fixed Top — The navbar is removed from document flow and pinned to the top of the viewport permanently. This means you must add padding-top to your body or first section to prevent content from hiding underneath it.

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
      ...
    </nav>
    
    <style>
      body {
        padding-top: 72px; /* match navbar height */
      }
    </style>

    Sticky Top — The navbar scrolls with the page until it reaches the top, then sticks. It stays in the document flow, so no body padding hack is needed.

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
      ...
    </nav>

    Use fixed-top when you want the navbar always visible regardless of page position — ideal for single-page apps. Use sticky-top for content-heavy pages like blogs or documentation where the user might want to scroll past the hero before the nav sticks.

    Pattern 2 — Transparent Overlay Navbar Over Hero Images

    This is one of the most visually impactful navbar techniques — overlaying the nav on a full-height hero section with a transparent background that transitions to solid on scroll. It’s a staple of landing pages built with Canvas Template and requires a small combination of CSS and JavaScript.

    <nav class="navbar navbar-expand-lg navbar-dark fixed-top" id="siteNav">
      <div class="container">
        <a class="navbar-brand" href="#">Brand</a>
        <!-- nav items -->
      </div>
    </nav>
    
    <style>
      #siteNav {
        background-color: transparent;
        transition: background-color 0.3s ease, box-shadow 0.3s ease;
      }
      #siteNav.scrolled {
        background-color: #1a1a2e;
        box-shadow: 0 2px 20px rgba(0,0,0,0.15);
      }
    </style>
    
    <script>
      window.addEventListener('scroll', function () {
        const nav = document.getElementById('siteNav');
        if (window.scrollY > 60) {
          nav.classList.add('scrolled');
        } else {
          nav.classList.remove('scrolled');
        }
      });
    </script>

    This pattern works brilliantly with full-screen hero sections. If you’re building a landing page and want to see it in context, check out our guide on How to Build a Landing Page with Bootstrap 5 in Under 1 Hour — the transparent navbar is one of the first elements we set up there.

    Pattern 3 — Custom Colour Schemes Using CSS Variables

    Bootstrap 5 replaced the old SASS-only theming approach with CSS custom properties for many components. The navbar in Bootstrap 5.3+ uses component-level CSS variables that you can override without touching a single SASS file.

    <nav class="navbar navbar-expand-lg" id="customNav">
      ...
    </nav>
    
    <style>
      #customNav {
        --bs-navbar-color: #e2e8f0;
        --bs-navbar-hover-color: #ffffff;
        --bs-navbar-active-color: #63b3ed;
        --bs-navbar-brand-color: #ffffff;
        --bs-navbar-brand-hover-color: #63b3ed;
        --bs-navbar-toggler-border-color: rgba(255,255,255,0.2);
        background-color: #1e3a5f;
        padding: 1rem 0;
      }
    </style>

    This approach is far cleaner than overriding deeply nested selectors. It also plays well with Bootstrap’s dark mode system introduced in v5.3. For a deeper look at customising templates without breaking things, read our post on How to Customise a Bootstrap 5 HTML Template Without Breaking It — the same CSS variable philosophy applies to navbars.

    Pattern 4 — Building a Mega Menu with Bootstrap 5

    Mega menus are essential for content-rich websites — SaaS products, e-commerce stores, agency sites with large service portfolios. Bootstrap 5 doesn’t include a mega menu out of the box, but you can build one cleanly by combining the dropdown component with the grid system.

    <li class="nav-item dropdown position-static">
      <a class="nav-link dropdown-toggle" href="#"
         data-bs-toggle="dropdown"
         aria-expanded="false">Solutions</a>
      <div class="dropdown-menu w-100 mt-0 rounded-0 border-0 shadow-lg p-4">
        <div class="row g-4">
          <div class="col-lg-3">
            <h6 class="text-uppercase text-muted small fw-semibold mb-3">Product</h6>
            <a class="dropdown-item px-0" href="#">Features</a>
            <a class="dropdown-item px-0" href="#">Pricing</a>
            <a class="dropdown-item px-0" href="#">Changelog</a>
          </div>
          <div class="col-lg-3">
            <h6 class="text-uppercase text-muted small fw-semibold mb-3">Company</h6>
            <a class="dropdown-item px-0" href="#">About Us</a>
            <a class="dropdown-item px-0" href="#">Blog</a>
            <a class="dropdown-item px-0" href="#">Careers</a>
          </div>
          <div class="col-lg-6 bg-light rounded p-3">
            <h6 class="fw-semibold mb-2">Featured Resource</h6>
            <p class="text-muted small">Get started with our quick-start guide and launch in minutes.</p>
            <a href="#" class="btn btn-sm btn-primary">Read Guide →</a>
          </div>
        </div>
      </div>
    </li>
    
    <style>
      .navbar .dropdown-menu {
        left: 0;
        right: 0;
        top: 100%;
      }
      .navbar .position-static {
        position: static !important;
      }
    </style>

    The key trick here is position-static on the parent li — this makes the dropdown menu position itself relative to the navbar container instead of the list item, giving you full-width behaviour. Canvas Template ships with pre-built mega menu components that follow exactly this pattern, so you won’t need to build from scratch if you’re working with it.

    A navbar that ends with a prominent CTA button converts far better than one that simply lists links. Bootstrap 5 makes it straightforward to add buttons and inline forms.

    <div class="collapse navbar-collapse" id="mainNav">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item"><a class="nav-link" href="#">Features</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Pricing</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Blog</a></li>
      </ul>
    
      <!-- Inline search form -->
      <form class="d-flex me-3" role="search">
        <input class="form-control form-control-sm me-2"
               type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-secondary btn-sm" type="submit">Go</button>
      </form>
    
      <!-- CTA Buttons -->
      <a href="#" class="btn btn-outline-primary btn-sm me-2">Log In</a>
      <a href="#" class="btn btn-primary btn-sm">Start Free Trial</a>
    </div>

    On mobile, these elements collapse inside the hamburger menu by default. If you want the CTA button to always be visible outside the collapse, place it between the toggler button and the collapse div — Bootstrap 5’s flex layout will handle the positioning correctly.

    <!-- Always-visible mobile CTA (outside collapse) -->
    <a href="#" class="btn btn-primary btn-sm ms-auto me-2 d-lg-none">Get Started</a>
    <button class="navbar-toggler" ...>...</button>

    Pattern 6 — Dark Mode-Aware Navbar with Bootstrap 5.3

    Bootstrap 5.3 introduced native dark mode support via the data-bs-theme attribute. Navbars can now adapt automatically to light and dark themes without any custom JavaScript.

    <!-- Explicit dark navbar -->
    <nav class="navbar navbar-expand-lg bg-body-tertiary" data-bs-theme="dark">
      ...
    </nav>
    
    <!-- Or on the root HTML element for global dark mode -->
    <html data-bs-theme="dark">

    For a toggle button that switches the entire page between light and dark:

    <button id="themeToggle" class="btn btn-sm btn-outline-secondary ms-3">
      Toggle Theme
    </button>
    
    <script>
      const toggleBtn = document.getElementById('themeToggle');
      toggleBtn.addEventListener('click', function () {
        const html = document.documentElement;
        const current = html.getAttribute('data-bs-theme');
        html.setAttribute('data-bs-theme', current === 'dark' ? 'light' : 'dark');
      });
    </script>

    Combined with CSS custom properties from Pattern 3, you can create a navbar that has completely different colour palettes for each mode — all driven by the single data-bs-theme attribute change.

    The navbar-brand element accepts images, SVGs, text, or combinations of both. Getting this right is a branding fundamental. Typography choices in your navbar should align with your overall type scale — something we cover in depth in our Bootstrap 5 Typography: Fonts, Sizes, and Hierarchy Explained guide.

    <!-- Logo + wordmark combination -->
    <a class="navbar-brand d-flex align-items-center gap-2" href="#">
      <img src="logo.svg" alt="Brand Logo" width="32" height="32">
      <span class="fw-bold fs-5 text-white">BrandName</span>
    </a>
    
    <!-- Retina-ready logo using CSS -->
    <style>
      .navbar-brand img {
        height: 36px;
        width: auto;
      }
      @media (min-width: 992px) {
        .navbar-brand img {
          height: 44px;
        }
      }
    </style>

    Always define the width and height attributes on your logo image to prevent layout shifts. Use SVG format wherever possible for crisp rendering at any resolution.

    Pattern 8 — Accessible Off-Canvas Navbar for Mobile

    The traditional collapse hamburger menu works fine, but for complex navigation structures, Bootstrap 5’s off-canvas component delivers a superior mobile experience. It slides in from the side, has a proper overlay backdrop, and handles focus trapping for accessibility.

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="#">Brand</a>
    
        <!-- Offcanvas trigger -->
        <button class="navbar-toggler" type="button"
                data-bs-toggle="offcanvas"
                data-bs-target="#mobileNav"
                aria-controls="mobileNav"
                aria-label="Open navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
    
        <!-- Desktop nav -->
        <div class="d-none d-lg-flex ms-auto align-items-center gap-4">
          <a class="nav-link text-white" href="#">Features</a>
          <a class="nav-link text-white" href="#">Pricing</a>
          <a href="#" class="btn btn-primary btn-sm">Get Started</a>
        </div>
      </div>
    </nav>
    
    <!-- Off-canvas panel -->
    <div class="offcanvas offcanvas-end text-bg-dark"
         tabindex="-1"
         id="mobileNav"
         aria-labelledby="mobileNavLabel">
      <div class="offcanvas-header">
        <h5 class="offcanvas-title" id="mobileNavLabel">Menu</h5>
        <button type="button" class="btn-close btn-close-white"
                data-bs-dismiss="offcanvas"
                aria-label="Close"></button>
      </div>
      <div class="offcanvas-body">
        <ul class="navbar-nav flex-column gap-2">
          <li class="nav-item"><a class="nav-link" href="#">Features</a></li>
          <li class="nav-item"><a class="nav-link" href="#">Pricing</a></li>
          <li class="nav-item"><a class="nav-link" href="#">Blog</a></li>
        </ul>
        <a href="#" class="btn btn-primary w-100 mt-4">Get Started</a>
      </div>
    </div>

    The off-canvas approach is particularly powerful for mobile mega-menus where you want accordion-style nested navigation. Bootstrap handles all the focus management and ARIA states automatically — just make sure your aria-label attributes are descriptive for screen reader users.

    Pattern Best Use Case JS Required? Mobile Friendly? Complexity
    Sticky Top Content-heavy pages No Yes Low
    Fixed Top Single-page apps No Yes Low
    Transparent Overlay Landing pages with hero sections Yes (minimal) Yes Medium
    Custom CSS Variables Branded colour schemes No Yes Low
    Mega Menu Large-content sites, SaaS No (Bootstrap handles it) Partial High
    CTA Button in Nav Conversion-focused pages No Yes Low
    Dark Mode Aware Apps with theme switching Optional Yes Low-Medium
    Off-Canvas Mobile Complex nav on mobile No (Bootstrap handles it) Excellent Medium

    Frequently Asked Questions

    How do I change the Bootstrap 5 navbar breakpoint?

    Replace navbar-expand-lg with navbar-expand-sm, navbar-expand-md, or navbar-expand-xl depending on when you want the navbar to expand from the hamburger view to the full horizontal layout. Use navbar-expand (without a breakpoint suffix) to always show the expanded layout.

    How do I centre navbar items in Bootstrap 5?

    Use mx-auto on your ul.navbar-nav element to centre the navigation links. If you have a brand on the left and a CTA on the right, use a combination of me-auto and ms-auto on separate nav sections to achieve a three-column layout.

    Can I use multiple navbars on the same page?

    Yes. Bootstrap 5 navbars are scoped by class, not global IDs — you can stack a top utility bar and a main navbar, for example. Just ensure each collapsible section has a unique id that matches its corresponding data-bs-target attribute on the toggler button.

    How do I make the Bootstrap navbar logo change on scroll?

    Use the same scroll event listener from Pattern 2. Store two versions of your logo (light and dark) and swap the src attribute of the img tag when the scrolled class is added. Alternatively, use two absolutely positioned logo images and toggle their visibility with CSS transitions — smoother and avoids a flash.

    Is the Bootstrap 5 navbar accessible by default?

    Bootstrap 5’s navbar includes ARIA attributes by default — aria-expanded on the toggler and aria-controls linking to the collapse target. However, you should always add a meaningful aria-label to your nav element (e.g., aria-label="Main navigation") and ensure dropdown menus are keyboard navigable. The off-canvas pattern (Pattern 8) handles focus trapping automatically, making it the most accessible mobile nav option.


    Final Thoughts

    The Bootstrap 5 navbar is genuinely one of the framework’s most production-ready components — but only if you know how to drive it. These eight patterns cover the vast majority of real-world requirements: from the basics of sticky positioning to advanced mega menus, scroll-aware transparency, dark mode support, and accessible off-canvas panels for mobile users.

    The real power comes from combining patterns. A landing page might use the transparent overlay technique (Pattern 2), CSS variable colour theming (Pattern 3), a CTA button (Pattern 5), and the off-canvas mobile panel (Pattern 8) all at once. Each layer adds polish without adding bloat.

    If you want a head start with all of these patterns pre-built and production-tested, Canvas Template ships with multiple navbar variants already wired up — including mega menus, transparent variants, and dark/light switchers. And if you’d rather skip the template setup entirely, CanvasBuilder is the AI website builder that generates Bootstrap 5 layouts — including full navbar configurations — from a simple prompt. Both tools are designed to get you from idea to live site faster, without compromising on code quality.

    Ready to build?
    Explore the full Canvas Template library at canvastemplate.com or try the AI-powered builder at canvasbuilder.co.