Problem & Common Pitfalls
When a table contains many rows, the header often scrolls away, making column headings hard to read. A naive approach of setting overflow on the <tbody> breaks column alignment because each row becomes a block element. The result is a messy layout where columns shift or wrap unexpectedly.
Pure CSS Fix for Fixed Header
Wrap the table in a container, give the <thead> a fixed height, and make the <tbody> scrollable. Use table-layout:fixed to keep column widths stable and display:block on thead and tbody. The following CSS achieves a clean, column‑aligned scrollable body:
.scroll-table {
width: 100%;
table-layout: fixed;
}
.scroll-table thead {
display: block;
width: 100%;
}
.scroll-table tbody {
display: block;
height: 250px;
overflow-y: auto;
}
.scroll-table th, .scroll-table td {
width: 16.66%; /* 6 columns */
text-align: left;
}
.scroll-table thead tr, .scroll-table tbody tr {
display: flex;
}
.scroll-table thead th, .scroll-table tbody td {
flex: 1;
}
Responsive Wrapper Trick
Bootstrap’s .table-responsive class adds horizontal scrolling on small screens. Combine it with the above CSS to keep the header fixed while the body scrolls vertically:
<div class="table-responsive">
<table class="table table-hover scroll-table">
<thead>…</thead>
<tbody>…</tbody>
</table>
</div>
Alternative with Flexbox
If you prefer not to use display:block on table elements, wrap rows in a flex container. This method keeps the semantic <table> structure and works well with Bootstrap’s grid utilities:
.flex-table {
display: flex;
flex-direction: column;
width: 100%;
}
.flex-table thead {
flex: 0 0 auto;
}
.flex-table tbody {
flex: 1 1 auto;
overflow-y: auto;
height: 250px;
}
.flex-table tr {
display: flex;
}
.flex-table th, .flex-table td {
flex: 1;
min-width: 0;
}
Performance & Accessibility Tips
Avoid heavy JavaScript for scrolling; pure CSS is faster and more accessible. Ensure the table has a <caption> for screen readers, use scope="col" on <th> elements, and keep the header row short to reduce vertical space. Test the layout in Chrome, Firefox, and Safari to confirm consistent column widths.
Takeaway: Use table-layout:fixed with display:block on thead and tbody, or a flexbox wrapper, to create a clean scrollable Bootstrap table with a fixed header.
People also ask
Can I use this technique with Bootstrap 5?
Yes. The same CSS applies; just replace .table-hover with the appropriate Bootstrap 5 classes.
Does this affect pagination?
No. The scrollable body is independent of pagination logic; you can still paginate server‑side or client‑side.
Will this work with dynamic column widths?
If columns vary, use width: auto and table-layout:auto, but you must recalc widths on resize or use JavaScript to sync header and body cells.
Inspired by a public discussion on Stack Overflow. This article is an original explanation for learners.