Building a complete page with Bootstrap

Assemble a marketing landing page and a dashboard shell, combining the grid, cards, navbar and tables — plus the composition mistakes that cost the most time.

Marketing landing page

<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom">
  <div class="container">
    <a class="navbar-brand fw-semibold" href="/">Acme</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav"
            aria-controls="nav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="nav">
      <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
        <li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
        <li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
      </ul>
      <a class="btn btn-primary ms-lg-3" href="/signup/">Start free</a>
    </div>
  </div>
</nav>

<header class="py-5 py-lg-6 bg-body-tertiary">
  <div class="container py-4">
    <div class="row align-items-center g-5">
      <div class="col-lg-6">
        <p class="text-uppercase fw-semibold text-primary small mb-2">Now in beta</p>
        <h1 class="display-5 fw-bold text-balance">Ship a responsive interface this week</h1>
        <p class="lead text-body-secondary">A component library, a grid and a theme system in one dependency.</p>
        <div class="d-flex flex-wrap gap-2">
          <a class="btn btn-primary btn-lg" href="/signup/">Start free</a>
          <a class="btn btn-outline-secondary btn-lg" href="#docs">Read the docs</a>
        </div>
      </div>
      <div class="col-lg-6">
        <div class="ratio ratio-4x3 rounded shadow-sm overflow-hidden bg-body">
          <img src="/img/product.webp" alt="Screenshot of the dashboard" class="object-fit-cover" width="1200" height="900">
        </div>
      </div>
    </div>
  </div>
</header>

<section id="features" class="py-5">
  <div class="container">
    <div class="row row-cols-1 row-cols-md-3 g-4">
      <div class="col"><div class="card h-100 border-0 shadow-sm"><div class="card-body">
        <span class="badge text-bg-primary mb-2">Grid</span>
        <h2 class="h5 card-title">Twelve columns, six breakpoints</h2>
        <p class="card-text text-body-secondary">Layouts that hold together from 320px to ultrawide.</p>
      </div></div></div>
    </div>
  </div>
</section>
  • py-lg-6 does not exist — Bootstrap stops at 5. Use an arbitrary value or a reusable .section class with your own padding.
  • The hero uses row plus column classes rather than hand-written flex, so reordering content on mobile is one class change.
  • text-balance prevents an awkward single-word last line in the headline; it degrades to normal wrapping where unsupported.
  • Every section sits directly inside a container. Nesting containers is a common mistake that produces doubled gutters.

Dashboard shell

<div class="d-flex flex-column min-vh-100 bg-body-tertiary">
  <header class="navbar navbar-expand bg-body border-bottom sticky-top px-3">
    <a class="navbar-brand mb-0" href="/">Acme</a>
    <form class="d-none d-md-flex ms-auto me-3" role="search">
      <input class="form-control form-control-sm" type="search" placeholder="Search" aria-label="Search">
    </form>
    <div class="dropdown">
      <button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown"
              aria-expanded="false">Account</button>
      <ul class="dropdown-menu dropdown-menu-end">
        <li><a class="dropdown-item" href="/settings/">Settings</a></li>
        <li><a class="dropdown-item" href="/logout/">Sign out</a></li>
      </ul>
    </div>
  </header>

  <div class="d-flex flex-grow-1">
    <aside class="d-none d-lg-block bg-body border-end p-3 flex-shrink-0" style="width: 240px;">
      <ul class="nav nav-pills flex-column gap-1">
        <li class="nav-item"><a class="nav-link active" href="#">Overview</a></li>
        <li class="nav-item"><a class="nav-link" href="#">Invoices</a></li>
      </ul>
    </aside>

    <main class="flex-grow-1 p-3 p-lg-4">
      <div class="row g-3 mb-4">
        <div class="col-6 col-xl-3">
          <div class="card border-0 shadow-sm h-100"><div class="card-body">
            <p class="text-body-secondary text-uppercase small mb-1">Revenue</p>
            <p class="fs-3 fw-semibold mb-0">$84,120</p>
          </div></div>
        </div>
      </div>

      <div class="card border-0 shadow-sm">
        <div class="card-header bg-body d-flex justify-content-between align-items-center">
          <h2 class="h6 mb-0">Recent invoices</h2>
          <a class="btn btn-sm btn-outline-secondary" href="#">View all</a>
        </div>
        <div class="table-responsive">
          <table class="table table-hover align-middle mb-0">
            <thead class="table-light"><tr><th scope="col">Invoice</th><th scope="col" class="text-end">Total</th></tr></thead>
            <tbody><tr><th scope="row" class="fw-normal">#1042</th><td class="text-end">140.00</td></tr></tbody>
          </table>
        </div>
      </div>
    </main>
  </div>
</div>
MistakeSymptomFix
Container inside a containerContent narrower than the headerOne container per horizontal band
h-100 without a sized parentCards ignore the classMake the row's columns stretch, or drop it
Sticky inside overflow ancestorHeader scrolls awayMove the sticky element out of the scroller
Badge with only a colourMeaning lost in greyscaleAdd text or an icon inside the badge
Modal markup re-rendered per rowSlow page, duplicate idsOne modal outside the table, filled before show()
⚠️
The most expensive Bootstrap bug in practice is duplicate id attributes. Modals, collapses and dropdowns all resolve their targets by id, so a repeated id makes the wrong panel open — and it usually happens when a component is copied into a table row.

Composition checklist

  1. Decide the shell first: full-width band or fixed container, and whether the page scrolls as a whole or only the main region.
  2. Build mobile-first. Write the small-screen classes, then add md:-style prefix variants — not the reverse.
  3. Use the spacing scale consistently; if you reach for a pixel value, ask whether a custom component class would be clearer.
  4. Add the empty, loading and error states for every card and table before styling the happy path.
  5. Check both colour modes, a 320px viewport and a 200% zoom before calling the page done.
// One modal for a table, populated on demand: keeps ids unique and the DOM small
const table = document.querySelector('#invoices');
const modalEl = document.getElementById('invoiceModal');
modalEl.addEventListener('show.bs.modal', (event) => {
  const row = event.relatedTarget?.closest('tr');
  if (!row) return;
  modalEl.querySelector('.modal-title').textContent = row.dataset.invoice;
  modalEl.querySelector('.modal-body').textContent = row.dataset.summary;
});
table.addEventListener('click', (event) => {
  const trigger = event.target.closest('[data-bs-toggle="modal"]');
  if (trigger) bootstrap.Modal.getOrCreateInstance(modalEl).show();
});

FAQ

How do I keep a dashboard sidebar from pushing content off screen on mobile?
Hide it below the lg breakpoint with d-none d-lg-block and expose the same navigation in an offcanvas triggered from the header. Duplicating the markup is fine — it is better than a squeezed sidebar, and the offcanvas version is only in the DOM once.
Should the page or the main region scroll?
Page scroll for marketing content, since the browser's own scrolling, back button and find-in-page all behave predictably. A fixed shell with an inner scroller suits dashboards but needs a sticky header with the right z-index and a check that focus does not land on hidden content.

Layout beyond the grid: positioning and spacing recipes Accessibility with Bootstrap

Last refreshed 2026-09-18.