Geoff Maddock
Geoff Maddock

Reputation: 1802

Position sticky with multiple TH rows

Is there an effective way to use position: sticky with multiple th rows?

        <table id="table-example" class="table mw-850">
        <thead>
        <tr>
            <th></th>
            <th colspan="2" class="text-center" scope="col">Checked In</th>
            <th colspan="2" class="text-center" scope="col">Checked Out</th>
        </tr>
        <tr>
            <th scope="col" style="background-color: #fdfdfe;">Action</th>
            <th class="text-center" scope="col">Email</th>
            <th class="text-center" scope="col">Event</th>
            <th class="text-center" scope="col">Email</th>
            <th class="text-center" scope="col">Event</th>
        </tr>
        </thead>
        <tbody>

        ...

And CSS

.table thead th {
    position: sticky !important;
    top: -1px !important;
    z-index: 1;
}

When I scroll, the collapse on top of each other.

Upvotes: 0

Views: 101

Answers (1)

Dai
Dai

Reputation: 155045

Unfortunately, there's no built-in way in CSS that correctly handles "position: sticky, but relative to another sticky element", so when I needed to do this same thing earlier this month I used ResizeObserver in a script to adjust the top: offset of the second row.

Fortunately it's pretty straightforward to use:

window.addEventListener( 'DOMContentLoaded', setUpStickyHeaders );

function setUpStickyHeaders() {

    if( typeof window.ResizeObserver !== 'function' ) return;

    const headerFirstRow = document.querySelector( 'table > thead > tr:first-child' );

    const headerSecondRowCells = document.querySelectorAll( 'table > thead > tr:not(:first-child) > th' );

    function onHeaderFirstRowResize( entries, observer ) {        

        for( const entry of entries ) {
            if( entry !== headerFirstRow ) continue;

            const firstRowBottom = entry.contentRect.bottom;

            for( const th of headerSecondRowCells ) {
                th.style.top = firstRowBottom.toString() + 'px'; // I find I often have to add a few extra px here for it to look good though.
            }
        }
    }

    const ro = new ResizeObserver( onHeaderFirstRowResize );
    ro.observe( headerFirstRow );
}

This is supported by all major browsers except IE11 (of course) and Safari (both macOS Safari and iOS Safari), which does not currently support ResizeObserver but there's a good polyfill available that works flawlessly for me.

Upvotes: 1

Related Questions