I'm working on a React table where both the header and body exist within the same div. I need the table header to be position: sticky without setting a fixed height while ensuring that overflow-x: auto is applied to enable horizontal scrolling.
However, I'm unable to make the sticky header work correctly when scrolling horizontally. The header either doesn't stick or gets misaligned.
Here's my component:
<div className={styles.tableContainer}>
<Table
data={tableData}
maxRows={50}
columns={columns}
renderDataItem={renderDataItem}
className={styles.table}
headerClassName={styles.tableHeader}
headerItemClassName={styles.headerItem}
bodyRowClassName={styles.tableBodyRow}
dataClassName={styles.tableData}
loading={loading}
controlled
/>
</div>
scss
.table-container {
overflow-x: auto;
position: relative;
}
.table {
border-collapse: separate;
border-spacing: 0;
border-width: 0;
.table-header {
background-color: var(--color-bg-secondary);
border: none;
position: sticky;
top: 0;
z-index: 100;
}
}
Issue In a React table where both the header and body exist within the same container, you need:
The header to remain sticky at the top. The table to allow horizontal scrolling (overflow-x: auto). No fixed height for the header.
A common issue is that the header either doesn’t stick properly or becomes misaligned when scrolling horizontally.
And yesss!!, I finally got a way to solve this!! Solution The trick is to use two separate elements for the table container and the table header. Then, sync the horizontal scroll (scrollLeft) of the header with the main container using a scroll event listener.
const StickyTable = ({ data, columns, renderDataItem, loading }) => {
const tableContainerRef = useRef<HTMLDivElement>(null);
const tableHeaderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const tableContainer = tableContainerRef.current;
const tableHeader = tableHeaderRef.current;
if (!tableContainer || !tableHeader) return;
const handleScroll = () => {
tableHeader.scrollLeft = tableContainer.scrollLeft;
};
tableContainer.addEventListener("scroll", handleScroll);
return () => tableContainer.removeEventListener("scroll", handleScroll);
}, []);
return (
<div className="table-container" ref={tableContainerRef}>
<div className="table-header" ref={tableHeaderRef}>
{/* Render Table Header */}
</div>
<table className="table">
<tbody>
{data.map((item, index) => renderDataItem(item, index))}
</tbody>
</table>
</div>
);
};
export default StickyTable;
Hope this helpss you too.