Two-Axis Sticky: Sticky Row and Column in the Same Table
Sticky first column and header row without JavaScript
Locking both the first column and the header row of a wide table used to require JavaScript scroll syncing or a heavy table library. A change to position: sticky lets it track different scrollers on each axis when the wrapping element only scrolls on one of them.
const wrap = document .querySelector('.table-wrap');const head = document .querySelector('thead');window.addEventListener( 'scroll', () => { const r = wrap .getBoundingClientRect(); head.style.transform = `translateY(${ Math.max(0, -r.top)}px)`;});wrap.addEventListener( 'scroll', () => { document.querySelectorAll( '.first-col') .forEach(el => { el.style.transform = `translateX(${wrap.scrollLeft}px)`; });}); .table-wrap { overflow-x: auto; overflow-y: clip; }th:first-child,td:first-child { position: sticky; left: 0;}thead th { position: sticky; top: 0;} position sticky Browser Support
This feature is not Baseline because it does not work in some of the most widely-used browsers.
Not ready for production without a fallback.
Two scrollers, one rule
The first column sticks against the table wrapper. The header row sticks against the document. No JavaScript scroll syncing, no transform math.
Single-axis scrollers
Setting overflow-y: clip on the wrapper means it only scrolls on one axis. Vertical stickiness falls through to the next scroller (the document).
Pair with scroll-state
Add container-type: scroll-state to style stuck headers and stuck columns differently when they engage. No JavaScript scroll observers.
How it works
Pin both the first column and the header row of a wide table and only one of them actually stays put. position: sticky historically tracked the nearest ancestor scroller. If that scroller only moved horizontally, vertical stickiness had nothing to track and the header scrolled away with the rest of the page. The fix was JavaScript: listen to window scroll, listen to wrapper scroll, and translate the header and the first column to fake the missing stickiness.
A recent change to the overflow spec lets a container scroll on only one axis. Set overflow-x: auto for horizontal scrolling and overflow-y: clip to opt out of vertical scrolling on the same element. The wrapper is now a single-axis scroller. The first column still sticks against the wrapper on the inline axis. The header row, having no vertical scroller to track at this level, walks up the ancestor chain and sticks against the document on the block axis. Two scrollers, one CSS rule.
Status: available behind the experimental web platform features flag in Chrome 148. Treat this as a preview, not a production pattern. Browser support data on this page will update once the flag is removed.