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-6does not exist — Bootstrap stops at 5. Use an arbitrary value or a reusable.sectionclass with your own padding.- The hero uses
rowplus column classes rather than hand-written flex, so reordering content on mobile is one class change. text-balanceprevents 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>| Mistake | Symptom | Fix |
|---|---|---|
| Container inside a container | Content narrower than the header | One container per horizontal band |
h-100 without a sized parent | Cards ignore the class | Make the row's columns stretch, or drop it |
Sticky inside overflow ancestor | Header scrolls away | Move the sticky element out of the scroller |
| Badge with only a colour | Meaning lost in greyscale | Add text or an icon inside the badge |
| Modal markup re-rendered per row | Slow page, duplicate ids | One 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
- Decide the shell first: full-width band or fixed container, and whether the page scrolls as a whole or only the main region.
- Build mobile-first. Write the small-screen classes, then add
md:-style prefix variants — not the reverse. - Use the spacing scale consistently; if you reach for a pixel value, ask whether a custom component class would be clearer.
- Add the empty, loading and error states for every card and table before styling the happy path.
- 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.Related
Layout beyond the grid: positioning and spacing recipes Accessibility with Bootstrap
Last refreshed 2026-09-18.