Layout beyond the grid: positioning and spacing recipes

Build page shells with flex utilities, keep headers sticky without breaking scroll, and use positioning and viewport sizing deliberately.

Page shells without custom CSS

The grid handles content columns, but the outer frame of a page β€” a header, a sidebar, a scrolling main area β€” is a flex problem. Bootstrap ships the flex utilities for exactly this, and they compose better than hand-written CSS because they stay consistent with the spacing scale.

<div class="d-flex flex-column min-vh-100">
  <header class="navbar navbar-expand bg-body border-bottom flex-shrink-0">
    <div class="container-fluid"><a class="navbar-brand" href="/">Acme</a></div>
  </header>

  <div class="d-flex flex-grow-1 overflow-hidden">
    <aside class="border-end p-3 flex-shrink-0 d-none d-lg-block" style="width: 260px;">
      <nav class="nav flex-column gap-1">
        <a class="nav-link" href="#">Overview</a>
        <a class="nav-link" href="#">Reports</a>
      </nav>
    </aside>

    <main class="flex-grow-1 overflow-auto p-4">
      <h1 class="h3">Overview</h1>
      <p class="text-body-secondary">Scrollable content region.</p>
    </main>
  </div>
</div>
  • min-vh-100 makes the shell at least as tall as the viewport, so a short page still fills the screen.
  • flex-grow-1 on the middle row is what actually pushes the footer down; without it the row collapses to its content.
  • overflow-hidden on the row plus overflow-auto on main is what makes only the content scroll.
  • flex-shrink-0 stops narrow viewports from squashing a sidebar or header to nothing.
πŸ’‘
Bootstrap's flex utilities map directly to CSS flex properties, so a browser with a working inspector is the fastest reference: the compiled rule for flex-grow-1 is literally flex-grow: 1 !important.

Sticky versus fixed

UtilityBehaviourFits
position-staticNormal flowThe default; reset when a component forces positioning
position-relativeFlow preserved, offsets applyAnchor for absolutely positioned children
position-absoluteRemoved from flowBadges, close buttons, overlays inside a relative parent
position-fixedRelative to the viewportApp bars, floating action buttons
position-stickyRelative until an offset is reachedTable headers, sticky sub-navigation
<!-- sticky sub-nav inside a scrolling page -->
<div class="position-sticky top-0 bg-body py-2 border-bottom" style="z-index: 1020;">
  <span class="text-body-secondary small">Section navigation</span>
</div>

<!-- sticky table header inside a scroll container -->
<div class="table-responsive" style="max-height: 380px; overflow-y: auto;">
  <table class="table table-sm mb-0">
    <thead class="position-sticky top-0 bg-body"><tr><th scope="col">Order</th><th scope="col">Total</th></tr></thead>
    <tbody><tr><td>#1041</td><td>82.00</td></tr></tbody>
  </table>
</div>
  • position-sticky only works when no ancestor sets overflow: hidden, auto or scroll β€” that is the single most common reason sticky silently does nothing.
  • A sticky element stays inside its parent box, so a header stuck inside a short column stops sticking where that column ends.
  • Bootstrap's components set their own z-index: the navbar uses 1030, modals 1055, tooltips 1080. Pick a value that fits that ladder instead of guessing 9999.
<div class="position-relative">
  <button type="button" class="btn btn-primary">Inbox</button>
  <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">
    9<span class="visually-hidden">unread messages</span>
  </span>
</div>

Viewport units, spacing and overflow

/* Bootstrap gives you the building blocks; a page-specific rule is still the
   right tool when you need a literal value. */
.hero {
  min-height: 60vh;          /* NOT min-vh-60: Bootstrap has no such class */
  margin-top: calc(-1 * var(--bs-navbar-height, 3.5rem));
}
.kanban {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}
NeedUseNote
Height of the viewportvh-100Makes the whole page scroll on mobile; prefer min-vh-100
Width of the viewportvw-100Overflows when a scrollbar is present
Relative to the containing blockh-100, w-100Requires the parent to have a resolvable height
Page paddingp-3 p-md-4Spacing scale is 0-5 plus auto
Gaps in a flex or grid rowgap-3Preferred over margins on children β€” no collapsing surprises

Scrollbars are the hidden cost of viewport units: 100vw includes the scrollbar gutter while 100% does not, so a full-bleed banner built with vw-100 commonly produces a few pixels of horizontal scroll. Use w-100 on a full-width parent instead.

FAQ

Why does my sticky navbar jump when it sticks?
Something above it is collapsing β€” usually a margin that the sticky element was relying on, or the parent being a flex container with align-items: stretch. Give the navbar a fixed context with position-relative on its wrapper, or move the sticky class onto the wrapper rather than the inner navbar.
Should I use sticky or fixed for a header?
Sticky when the header should occupy space in the document (the normal case, and it avoids the padding hack). Fixed when the header must be outside normal flow entirely, for example on top of a full-bleed hero β€” then you must compensate with padding on the first section.

The grid system Building a complete page with Bootstrap

Last refreshed 2026-09-18.