Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bssticky-card">
    <div class="card-body p-3">
      <h6 class="fw-bold mb-2">Order history</h6>
      <div class="bssticky-scroll" id="bsstickyScroll">
        <table class="table table-sm mb-0">
          <thead>
            <tr>
              <th>Order</th>
              <th>Customer</th>
              <th>Total</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody id="bsstickyBody"></tbody>
        </table>
      </div>
    </div>
  </div>
</div>

Bootstrap Sticky Table Header on Scroll — Free HTML CSS JS Snippet

Bootstrap Sticky Table Header on Scroll · Tables · Plain HTML, CSS & JS · Live preview

What's included

Features

A genuinely sticky header using position: sticky scoped to its own scroll container, not the page
An opaque header background so scrolling rows never show through the pinned row
A JavaScript-driven shadow that only appears once real scroll has occurred, not a static decoration
Works with any number of rows — the header logic doesn't know or care how much data is in the table
No JavaScript is required for the sticky behavior itself, only for the scroll-aware shadow

About this UI Snippet

Bootstrap Sticky Table Header on Scroll — HTML, CSS & JavaScript

Screenshot of the Bootstrap Sticky Table Header on Scroll snippet rendered live

The header staying in place is entirely CSS — position: sticky; top: 0; on every thead th, scoped to the .bssticky-scroll container's own scrollbar rather than the page's. That scoping matters: position: sticky sticks relative to its nearest scrolling ancestor, so the container needs its own overflow-y: auto and a real height limit (max-height: 260px here) for the header to have anything to stick within — without that, the whole page would need to scroll before the header engaged at all.

The one piece of actual JavaScript exists purely to answer a question CSS can't: has the body content actually moved out from under the header yet? A single scroll listener toggles bssticky-scrolled based on scrollBox.scrollTop > 0, and that class is what applies the header's drop shadow — so the shadow only appears once there's genuinely something to visually separate the header from, rather than showing a permanent shadow that implies scrollable content even when the table is sitting at the very top.

A background color on the sticky th elements (background: #fff) is required for this to look right, not optional — without an opaque background, the header would stay positioned correctly but the table rows scrolling underneath it would show through, defeating the whole point of a header that's supposed to stay legible above moving content.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to also make the first column sticky (frozen) alongside the sticky header, so both a row's row-identifying column and the column headers stay visible while scrolling in both directions, or to add a subtle fade-out gradient at the bottom of the scroll box to hint that more rows exist below the fold.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 table with a sticky header inside its own scrollable container, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js), not custom CSS made to resemble it.

Requirements:
- A table with several columns and at least 20 rows of sample data, wrapped in a container with a fixed max-height and overflow-y: auto.
- The table header (thead th cells) must use position: sticky with top: 0 and an explicit opaque background color, so it stays pinned to the top of the scroll container while the body scrolls underneath it, without any content showing through.
- Add a scroll event listener on the scroll container that toggles a CSS class adding a subtle drop shadow under the header, only once the container's scrollTop is greater than zero — the shadow should disappear again when scrolled back to the very top.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetA 4-column table appears with 24 rows inside a fixed-height scroll box, header at the top with no shadow.
  2. 2
    Scroll down inside the tableThe column headers stay pinned to the top of the box while every row scrolls underneath them.
  3. 3
    Watch the header as you start scrollingA subtle drop shadow fades in under the header the instant scrollTop leaves zero.
  4. 4
    Scroll back to the very topThe shadow disappears again, since there's nothing left scrolled underneath the header.

Real-world uses

Common Use Cases

Dashboard and admin data tables
Any table with more rows than fit on screen at once benefits from a header that stays legible — pairs with bootstrap-sortable-data-table.
Order, transaction, or activity history panels
A compact card showing recent activity where scrolling to older entries shouldn't lose the column labels.
Data-entry grids and spreadsheet-style UIs
Column context stays visible no matter how far down a long entry list a user has scrolled.
Learning position: sticky's actual requirements
A working example of the two things sticky headers commonly get wrong: needing a scoped scroll container, and needing an opaque background.

Got questions?

Frequently Asked Questions

position: sticky sticks relative to the nearest ancestor with real, scrollable overflow. Without a bounded height and overflow-y: auto on .bssticky-scroll, the container never scrolls on its own, so the header has no scroll context to stick within — the browser would fall back to normal static positioning.

A sticky element keeps its position but not automatic opacity — without a solid background, table rows scrolling past would visibly show through the header text, since sticky elements paint in normal document flow rather than as an overlay.

The same position: sticky mechanism extends to a first column (with left: 0 instead of top: 0) for a frozen-column effect, though combining sticky rows and sticky columns for a fully frozen corner cell needs a small amount of extra CSS to handle the overlap.

Yes — the sticky behavior is pure CSS and needs no framework changes at all; only the scroll-shadow toggle needs to move into a scroll event handler attached in a mount lifecycle hook (useEffect, onMounted, ngAfterViewInit).

Yes — position: sticky has had solid support in all major mobile browsers for years, including inside a scrollable div rather than only the page body.