Pure TailwindMIT licensed

Tailwind CSS Data Table with Sticky Header and Sticky First Column

The spreadsheet contract in utilities: a 10-column positions table scrolls both ways inside its panel while the header row pins to the top, the ticker column freezes to the left, and the corner cell wins both fights with a three-step z-10 / z-20 / z-30 ladder. New in the port: a sticky totals row pinned to the bottom with sticky bottom-0, and a Compact rows toggle that re-pads every cell through has-[#tdl14-dense:checked]:.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="tdl-14 w-full min-h-svh bg-tdl14-bg font-tdl14-sans text-tdl14-ink antialiased" aria-label="Sticky header and column data grid demo">
  <div class="flex h-svh min-h-[30rem] flex-col overflow-hidden bg-tdl14-panel has-[#tdl14-dense:checked]:[&_td]:py-1.5 has-[#tdl14-dense:checked]:[&_tbody_th]:py-1.5">
    <header class="flex flex-wrap items-center justify-between gap-3 border-b border-tdl14-edge px-4.5 py-3.5">
      <div><h2 class="text-base font-bold tracking-tight">Portfolio — live positions</h2><p class="text-xs text-tdl14-mut">24 holdings · scroll down and across; header, tickers and totals stay pinned</p></div>
      <label class="flex h-9 cursor-pointer items-center gap-2.5 rounded-full border border-tdl14-edge bg-tdl14-bg pr-3.5 pl-1.5 text-xs font-semibold text-tdl14-mut transition has-checked:border-tdl14-acc/40 has-checked:text-tdl14-acc has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tdl14-acc"><input class="peer sr-only" type="checkbox" id="tdl14-dense"><span aria-hidden="true" class="relative h-5 w-8.5 rounded-full bg-tdl14-edge transition-colors peer-checked:bg-tdl14-acc after:absolute after:top-0.5 after:left-0.5 after:size-4 after:rounded-full after:bg-white after:shadow after:transition-transform after:content-[''] peer-checked:after:translate-x-3.5"></span>Compact rows</label>
    </header>
    <div role="region" aria-label="Positions table, scrollable" tabindex="0" class="min-h-0 flex-1 overflow-auto overscroll-contain [scrollbar-color:var(--color-tdl14-edge)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tdl14-acc">
      <table class="w-full min-w-[60rem] border-separate border-spacing-0 text-[13px] tabular-nums [&_td]:border-b [&_td]:border-tdl14-edge/60 [&_td]:bg-tdl14-panel [&_td]:px-3.5 [&_td]:py-2.5 [&_td]:text-right [&_td]:whitespace-nowrap [&_td]:transition-[padding] [&_td:last-child]:text-left [&_th]:whitespace-nowrap [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-20 [&_thead_th]:border-b [&_thead_th]:border-tdl14-edge [&_thead_th]:bg-tdl14-bg [&_thead_th]:px-3.5 [&_thead_th]:py-2.5 [&_thead_th]:text-right [&_thead_th]:text-[10.5px] [&_thead_th]:font-bold [&_thead_th]:tracking-[0.09em] [&_thead_th]:text-tdl14-mut [&_thead_th]:uppercase [&_thead_th:first-child]:left-0 [&_thead_th:first-child]:z-30 [&_thead_th:first-child]:text-left [&_thead_th:first-child]:shadow-[inset_-8px_0_8px_-8px_var(--color-tdl14-shadow)] [&_thead_th:last-child]:text-left [&_tbody_th]:sticky [&_tbody_th]:left-0 [&_tbody_th]:z-10 [&_tbody_th]:w-24 [&_tbody_th]:min-w-24 [&_tbody_th]:border-b [&_tbody_th]:border-tdl14-edge/60 [&_tbody_th]:bg-tdl14-panel [&_tbody_th]:px-3.5 [&_tbody_th]:py-2.5 [&_tbody_th]:text-left [&_tbody_th]:font-tdl14-mono [&_tbody_th]:text-[12.5px] [&_tbody_th]:font-bold [&_tbody_th]:text-tdl14-acc [&_tbody_th]:shadow-[inset_-8px_0_8px_-8px_var(--color-tdl14-shadow)] [&_tbody_th]:transition-[padding] [&_tbody_tr:nth-child(even)>*]:bg-tdl14-zebra [&_tbody_tr:hover>*]:bg-tdl14-hover [&_tfoot_*]:sticky [&_tfoot_*]:bottom-0 [&_tfoot_*]:z-20 [&_tfoot_*]:border-t [&_tfoot_*]:border-tdl14-edge [&_tfoot_*]:bg-tdl14-foot [&_tfoot_*]:px-3.5 [&_tfoot_*]:py-3 [&_tfoot_*]:font-bold [&_tfoot_th]:left-0 [&_tfoot_th]:z-30 [&_tfoot_th]:text-left [&_tfoot_th]:text-[10.5px] [&_tfoot_th]:tracking-[0.09em] [&_tfoot_th]:text-tdl14-mut [&_tfoot_th]:uppercase">
        <caption class="sr-only">Live positions: price, daily change, position size, market value, profit and loss, weight, beta and 52-week range, with portfolio totals</caption>
        <thead><tr><th scope="col">Ticker</th><th scope="col">Price</th><th scope="col">Δ Day</th><th scope="col">Δ %</th><th scope="col">Shares</th><th scope="col">Mkt value</th><th scope="col">P/L</th><th scope="col">Weight</th><th scope="col">Beta</th><th scope="col">52-wk range</th></tr></thead>
        <tbody>
          <tr><th scope="row">NVAX</th><td>$214.32</td><td class="font-semibold text-tdl14-up">+3.41</td><td class="font-semibold text-tdl14-up">+1.62%</td><td>420</td><td>$90,014</td><td class="font-semibold text-tdl14-up">+$12,480</td><td>18.2%</td><td>1.24</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:72%"></i></span></td></tr>
          <tr><th scope="row">ARDN</th><td>$88.10</td><td class="font-semibold text-tdl14-dn">−1.02</td><td class="font-semibold text-tdl14-dn">−1.14%</td><td>1,150</td><td>$101,315</td><td class="font-semibold text-tdl14-up">+$8,905</td><td>20.5%</td><td>0.92</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:44%"></i></span></td></tr>
          <tr><th scope="row">SOLV</th><td>$41.77</td><td class="font-semibold text-tdl14-up">+0.66</td><td class="font-semibold text-tdl14-up">+1.60%</td><td>2,400</td><td>$100,248</td><td class="font-semibold text-tdl14-dn">−$3,120</td><td>20.3%</td><td>1.61</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:31%"></i></span></td></tr>
          <tr><th scope="row">HXGN</th><td>$132.09</td><td class="font-semibold text-tdl14-up">+2.18</td><td class="font-semibold text-tdl14-up">+1.68%</td><td>610</td><td>$80,575</td><td class="font-semibold text-tdl14-up">+$15,230</td><td>16.3%</td><td>1.05</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:86%"></i></span></td></tr>
          <tr><th scope="row">PLTX</th><td>$67.45</td><td class="font-semibold text-tdl14-dn">−0.34</td><td class="font-semibold text-tdl14-dn">−0.50%</td><td>890</td><td>$60,031</td><td class="font-semibold text-tdl14-up">+$4,010</td><td>12.1%</td><td>1.44</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:58%"></i></span></td></tr>
          <tr><th scope="row">QBIT</th><td>$18.92</td><td class="font-semibold text-tdl14-up">+0.87</td><td class="font-semibold text-tdl14-up">+4.82%</td><td>3,300</td><td>$62,436</td><td class="font-semibold text-tdl14-dn">−$1,890</td><td>12.6%</td><td>2.10</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:12%"></i></span></td></tr>
          <tr><th scope="row">KRYP</th><td>$305.14</td><td class="font-semibold text-tdl14-up">+5.22</td><td class="font-semibold text-tdl14-up">+1.74%</td><td>260</td><td>$79,336</td><td class="font-semibold text-tdl14-up">+$18,940</td><td>16.1%</td><td>1.35</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:68%"></i></span></td></tr>
          <tr><th scope="row">LMND</th><td>$52.08</td><td class="font-semibold text-tdl14-dn">−1.14</td><td class="font-semibold text-tdl14-dn">−2.14%</td><td>1,780</td><td>$92,702</td><td class="font-semibold text-tdl14-dn">−$6,410</td><td>18.7%</td><td>1.72</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:22%"></i></span></td></tr>
          <tr><th scope="row">MRVL</th><td>$88.72</td><td class="font-semibold text-tdl14-up">+0.94</td><td class="font-semibold text-tdl14-up">+1.07%</td><td>620</td><td>$55,006</td><td class="font-semibold text-tdl14-up">+$3,140</td><td>11.1%</td><td>1.18</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:54%"></i></span></td></tr>
          <tr><th scope="row">OCTA</th><td>$142.55</td><td class="font-semibold text-tdl14-dn">−2.03</td><td class="font-semibold text-tdl14-dn">−1.40%</td><td>340</td><td>$48,467</td><td class="font-semibold text-tdl14-dn">−$4,220</td><td>9.8%</td><td>1.42</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:38%"></i></span></td></tr>
          <tr><th scope="row">PRSM</th><td>$28.60</td><td class="font-semibold text-tdl14-up">+0.55</td><td class="font-semibold text-tdl14-up">+1.96%</td><td>2,200</td><td>$62,920</td><td class="font-semibold text-tdl14-up">+$5,610</td><td>12.7%</td><td>1.61</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:61%"></i></span></td></tr>
          <tr><th scope="row">QSAT</th><td>$71.32</td><td class="font-semibold text-tdl14-up">+1.87</td><td class="font-semibold text-tdl14-up">+2.69%</td><td>520</td><td>$37,086</td><td class="font-semibold text-tdl14-up">+$2,740</td><td>7.5%</td><td>1.09</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:48%"></i></span></td></tr>
          <tr><th scope="row">RVLR</th><td>$14.02</td><td class="font-semibold text-tdl14-dn">−0.28</td><td class="font-semibold text-tdl14-dn">−1.96%</td><td>4,400</td><td>$61,688</td><td class="font-semibold text-tdl14-dn">−$3,120</td><td>12.4%</td><td>1.88</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:19%"></i></span></td></tr>
          <tr><th scope="row">SNTX</th><td>$96.44</td><td class="font-semibold text-tdl14-up">+1.31</td><td class="font-semibold text-tdl14-up">+1.38%</td><td>480</td><td>$46,291</td><td class="font-semibold text-tdl14-up">+$6,830</td><td>9.3%</td><td>1.14</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:76%"></i></span></td></tr>
          <tr><th scope="row">TRVN</th><td>$213.89</td><td class="font-semibold text-tdl14-dn">−1.72</td><td class="font-semibold text-tdl14-dn">−0.80%</td><td>210</td><td>$44,917</td><td class="font-semibold text-tdl14-up">+$1,940</td><td>9.1%</td><td>0.98</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:52%"></i></span></td></tr>
          <tr><th scope="row">UBQT</th><td>$8.44</td><td class="font-semibold text-tdl14-up">+0.36</td><td class="font-semibold text-tdl14-up">+4.45%</td><td>5,600</td><td>$47,264</td><td class="font-semibold text-tdl14-dn">−$1,120</td><td>9.6%</td><td>2.24</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:15%"></i></span></td></tr>
          <tr><th scope="row">VEDA</th><td>$164.28</td><td class="font-semibold text-tdl14-up">+2.41</td><td class="font-semibold text-tdl14-up">+1.49%</td><td>390</td><td>$64,069</td><td class="font-semibold text-tdl14-up">+$7,420</td><td>13.0%</td><td>1.06</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:82%"></i></span></td></tr>
          <tr><th scope="row">WLKN</th><td>$45.66</td><td class="font-semibold text-tdl14-dn">−0.61</td><td class="font-semibold text-tdl14-dn">−1.32%</td><td>1,320</td><td>$60,271</td><td class="font-semibold text-tdl14-dn">−$2,290</td><td>12.2%</td><td>1.51</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:34%"></i></span></td></tr>
          <tr><th scope="row">XGRN</th><td>$29.85</td><td class="font-semibold text-tdl14-up">+0.72</td><td class="font-semibold text-tdl14-up">+2.47%</td><td>2,050</td><td>$61,193</td><td class="font-semibold text-tdl14-up">+$4,010</td><td>12.4%</td><td>1.29</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:58%"></i></span></td></tr>
          <tr><th scope="row">YOTA</th><td>$118.03</td><td class="font-semibold text-tdl14-up">+1.55</td><td class="font-semibold text-tdl14-up">+1.33%</td><td>470</td><td>$55,474</td><td class="font-semibold text-tdl14-up">+$3,820</td><td>11.2%</td><td>0.87</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:67%"></i></span></td></tr>
          <tr><th scope="row">ZPHR</th><td>$78.20</td><td class="font-semibold text-tdl14-dn">−0.98</td><td class="font-semibold text-tdl14-dn">−1.24%</td><td>710</td><td>$55,522</td><td class="font-semibold text-tdl14-dn">−$1,540</td><td>11.2%</td><td>1.36</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:41%"></i></span></td></tr>
          <tr><th scope="row">AXNT</th><td>$36.14</td><td class="font-semibold text-tdl14-up">+0.44</td><td class="font-semibold text-tdl14-up">+1.23%</td><td>1,850</td><td>$66,859</td><td class="font-semibold text-tdl14-up">+$5,110</td><td>13.5%</td><td>1.17</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:55%"></i></span></td></tr>
          <tr><th scope="row">BLTZ</th><td>$188.66</td><td class="font-semibold text-tdl14-up">+3.12</td><td class="font-semibold text-tdl14-up">+1.68%</td><td>300</td><td>$56,598</td><td class="font-semibold text-tdl14-up">+$9,210</td><td>11.5%</td><td>1.22</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:73%"></i></span></td></tr>
          <tr><th scope="row">CVGT</th><td>$52.88</td><td class="font-semibold text-tdl14-dn">−0.71</td><td class="font-semibold text-tdl14-dn">−1.32%</td><td>1,140</td><td>$60,283</td><td class="font-semibold text-tdl14-dn">−$2,780</td><td>12.2%</td><td>1.44</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:29%"></i></span></td></tr>
        </tbody>
        <tfoot><tr><th scope="row">Total</th><td></td><td></td><td></td><td></td><td>$1,550,565</td><td class="text-tdl14-up">+$82,905</td><td></td><td></td><td></td></tr></tfoot>
      </table>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tdl14-bg: #f6f7f9;
  --color-tdl14-bg: oklch(0.975 0.004 250);
  --color-tdl14-panel: #ffffff;
  --color-tdl14-panel: oklch(1 0 0);
  --color-tdl14-zebra: #fafbfc;
  --color-tdl14-zebra: oklch(0.987 0.003 250);
  --color-tdl14-hover: #eef3fa;
  --color-tdl14-hover: oklch(0.96 0.015 250);
  --color-tdl14-foot: #f0f3f7;
  --color-tdl14-foot: oklch(0.962 0.008 250);
  --color-tdl14-edge: #dfe3e9;
  --color-tdl14-edge: oklch(0.905 0.008 250);
  --color-tdl14-ink: #232933;
  --color-tdl14-ink: oklch(0.26 0.015 250);
  --color-tdl14-mut: #59606c;
  --color-tdl14-mut: oklch(0.48 0.015 250);
  --color-tdl14-acc: #2a59a6;
  --color-tdl14-acc: oklch(0.48 0.13 255);
  --color-tdl14-up: #157a45;
  --color-tdl14-up: oklch(0.5 0.12 155);
  --color-tdl14-dn: #c0352b;
  --color-tdl14-dn: oklch(0.54 0.18 27);
  --color-tdl14-track: #e6e9ee;
  --color-tdl14-track: oklch(0.925 0.008 250);
  --color-tdl14-shadow: #3c4350;
  --color-tdl14-shadow: oklch(0.3 0.02 250 / 0.25);
  --font-tdl14-sans: 'Red Hat Text', system-ui, sans-serif;
  --font-tdl14-mono: 'Red Hat Mono', ui-monospace, monospace;
}

.tdl-14 {
  width: 100%;
  min-height: 100svh;
}

@media (prefers-reduced-motion: reduce) {
  .tdl-14 * {
    transition: none !important;
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tdl14-bg: #f6f7f9;
  --color-tdl14-bg: oklch(0.975 0.004 250);
  --color-tdl14-panel: #ffffff;
  --color-tdl14-panel: oklch(1 0 0);
  --color-tdl14-zebra: #fafbfc;
  --color-tdl14-zebra: oklch(0.987 0.003 250);
  --color-tdl14-hover: #eef3fa;
  --color-tdl14-hover: oklch(0.96 0.015 250);
  --color-tdl14-foot: #f0f3f7;
  --color-tdl14-foot: oklch(0.962 0.008 250);
  --color-tdl14-edge: #dfe3e9;
  --color-tdl14-edge: oklch(0.905 0.008 250);
  --color-tdl14-ink: #232933;
  --color-tdl14-ink: oklch(0.26 0.015 250);
  --color-tdl14-mut: #59606c;
  --color-tdl14-mut: oklch(0.48 0.015 250);
  --color-tdl14-acc: #2a59a6;
  --color-tdl14-acc: oklch(0.48 0.13 255);
  --color-tdl14-up: #157a45;
  --color-tdl14-up: oklch(0.5 0.12 155);
  --color-tdl14-dn: #c0352b;
  --color-tdl14-dn: oklch(0.54 0.18 27);
  --color-tdl14-track: #e6e9ee;
  --color-tdl14-track: oklch(0.925 0.008 250);
  --color-tdl14-shadow: #3c4350;
  --color-tdl14-shadow: oklch(0.3 0.02 250 / 0.25);
  --font-tdl14-sans: 'Red Hat Text', system-ui, sans-serif;
  --font-tdl14-mono: 'Red Hat Mono', ui-monospace, monospace;
}

.tdl-14 {
  width: 100%;
  min-height: 100svh;
}

@media (prefers-reduced-motion: reduce) {
  .tdl-14 * {
    transition: none !important;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Red+Hat+Text:wght@400..700&family=Red+Hat+Mono:wght@500;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The 20-line sticky engine moved onto the <table> as descendant variants; row cells carry only text-tdl14-up/-dn where a value is coloured.
  • .cdl-14__sr became sr-only; the 52-week marker reads its position from inline --p via left-(--p).
  • UI upgrade from the CSS original: a sticky <tfoot> totals row (market value and P/L across all 24 positions) pins to the bottom edge, its corner on the same z-ladder; a Compact rows switch re-pads every cell with one has-[…:checked] variant.
  • UI upgrade: the hint chip became a proper subtitle; the 52-week range shows low/high ticks; hovered rows highlight including the frozen cell; muted and accent inks darkened for 6:1+.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS Data Table with Sticky Header and Sticky First Column
Source: https://codefronts.com/layouts/tailwind-css-dashboard-layouts/tailwind-css-data-table-sticky-header-sticky-first-column/

The spreadsheet contract in utilities: a 10-column positions table scrolls both ways inside its panel while the header row pins to the top, the ticker column freezes to the left, and the corner cell wins both fights with a three-step z-10 / z-20 / z-30 ladder. New in the port: a sticky totals row pinned to the bottom with sticky bottom-0, and a Compact rows toggle that re-pads every cell through has-[#tdl14-dense:checked]:.
## HTML
```html
<section class="tdl-14 w-full min-h-svh bg-tdl14-bg font-tdl14-sans text-tdl14-ink antialiased" aria-label="Sticky header and column data grid demo">
  <div class="flex h-svh min-h-[30rem] flex-col overflow-hidden bg-tdl14-panel has-[#tdl14-dense:checked]:[&_td]:py-1.5 has-[#tdl14-dense:checked]:[&_tbody_th]:py-1.5">
    <header class="flex flex-wrap items-center justify-between gap-3 border-b border-tdl14-edge px-4.5 py-3.5">
      <div><h2 class="text-base font-bold tracking-tight">Portfolio — live positions</h2><p class="text-xs text-tdl14-mut">24 holdings · scroll down and across; header, tickers and totals stay pinned</p></div>
      <label class="flex h-9 cursor-pointer items-center gap-2.5 rounded-full border border-tdl14-edge bg-tdl14-bg pr-3.5 pl-1.5 text-xs font-semibold text-tdl14-mut transition has-checked:border-tdl14-acc/40 has-checked:text-tdl14-acc has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tdl14-acc"><input class="peer sr-only" type="checkbox" id="tdl14-dense"><span aria-hidden="true" class="relative h-5 w-8.5 rounded-full bg-tdl14-edge transition-colors peer-checked:bg-tdl14-acc after:absolute after:top-0.5 after:left-0.5 after:size-4 after:rounded-full after:bg-white after:shadow after:transition-transform after:content-[''] peer-checked:after:translate-x-3.5"></span>Compact rows</label>
    </header>
    <div role="region" aria-label="Positions table, scrollable" tabindex="0" class="min-h-0 flex-1 overflow-auto overscroll-contain [scrollbar-color:var(--color-tdl14-edge)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tdl14-acc">
      <table class="w-full min-w-[60rem] border-separate border-spacing-0 text-[13px] tabular-nums [&_td]:border-b [&_td]:border-tdl14-edge/60 [&_td]:bg-tdl14-panel [&_td]:px-3.5 [&_td]:py-2.5 [&_td]:text-right [&_td]:whitespace-nowrap [&_td]:transition-[padding] [&_td:last-child]:text-left [&_th]:whitespace-nowrap [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-20 [&_thead_th]:border-b [&_thead_th]:border-tdl14-edge [&_thead_th]:bg-tdl14-bg [&_thead_th]:px-3.5 [&_thead_th]:py-2.5 [&_thead_th]:text-right [&_thead_th]:text-[10.5px] [&_thead_th]:font-bold [&_thead_th]:tracking-[0.09em] [&_thead_th]:text-tdl14-mut [&_thead_th]:uppercase [&_thead_th:first-child]:left-0 [&_thead_th:first-child]:z-30 [&_thead_th:first-child]:text-left [&_thead_th:first-child]:shadow-[inset_-8px_0_8px_-8px_var(--color-tdl14-shadow)] [&_thead_th:last-child]:text-left [&_tbody_th]:sticky [&_tbody_th]:left-0 [&_tbody_th]:z-10 [&_tbody_th]:w-24 [&_tbody_th]:min-w-24 [&_tbody_th]:border-b [&_tbody_th]:border-tdl14-edge/60 [&_tbody_th]:bg-tdl14-panel [&_tbody_th]:px-3.5 [&_tbody_th]:py-2.5 [&_tbody_th]:text-left [&_tbody_th]:font-tdl14-mono [&_tbody_th]:text-[12.5px] [&_tbody_th]:font-bold [&_tbody_th]:text-tdl14-acc [&_tbody_th]:shadow-[inset_-8px_0_8px_-8px_var(--color-tdl14-shadow)] [&_tbody_th]:transition-[padding] [&_tbody_tr:nth-child(even)>*]:bg-tdl14-zebra [&_tbody_tr:hover>*]:bg-tdl14-hover [&_tfoot_*]:sticky [&_tfoot_*]:bottom-0 [&_tfoot_*]:z-20 [&_tfoot_*]:border-t [&_tfoot_*]:border-tdl14-edge [&_tfoot_*]:bg-tdl14-foot [&_tfoot_*]:px-3.5 [&_tfoot_*]:py-3 [&_tfoot_*]:font-bold [&_tfoot_th]:left-0 [&_tfoot_th]:z-30 [&_tfoot_th]:text-left [&_tfoot_th]:text-[10.5px] [&_tfoot_th]:tracking-[0.09em] [&_tfoot_th]:text-tdl14-mut [&_tfoot_th]:uppercase">
        <caption class="sr-only">Live positions: price, daily change, position size, market value, profit and loss, weight, beta and 52-week range, with portfolio totals</caption>
        <thead><tr><th scope="col">Ticker</th><th scope="col">Price</th><th scope="col">Δ Day</th><th scope="col">Δ %</th><th scope="col">Shares</th><th scope="col">Mkt value</th><th scope="col">P/L</th><th scope="col">Weight</th><th scope="col">Beta</th><th scope="col">52-wk range</th></tr></thead>
        <tbody>
          <tr><th scope="row">NVAX</th><td>$214.32</td><td class="font-semibold text-tdl14-up">+3.41</td><td class="font-semibold text-tdl14-up">+1.62%</td><td>420</td><td>$90,014</td><td class="font-semibold text-tdl14-up">+$12,480</td><td>18.2%</td><td>1.24</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:72%"></i></span></td></tr>
          <tr><th scope="row">ARDN</th><td>$88.10</td><td class="font-semibold text-tdl14-dn">−1.02</td><td class="font-semibold text-tdl14-dn">−1.14%</td><td>1,150</td><td>$101,315</td><td class="font-semibold text-tdl14-up">+$8,905</td><td>20.5%</td><td>0.92</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:44%"></i></span></td></tr>
          <tr><th scope="row">SOLV</th><td>$41.77</td><td class="font-semibold text-tdl14-up">+0.66</td><td class="font-semibold text-tdl14-up">+1.60%</td><td>2,400</td><td>$100,248</td><td class="font-semibold text-tdl14-dn">−$3,120</td><td>20.3%</td><td>1.61</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:31%"></i></span></td></tr>
          <tr><th scope="row">HXGN</th><td>$132.09</td><td class="font-semibold text-tdl14-up">+2.18</td><td class="font-semibold text-tdl14-up">+1.68%</td><td>610</td><td>$80,575</td><td class="font-semibold text-tdl14-up">+$15,230</td><td>16.3%</td><td>1.05</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:86%"></i></span></td></tr>
          <tr><th scope="row">PLTX</th><td>$67.45</td><td class="font-semibold text-tdl14-dn">−0.34</td><td class="font-semibold text-tdl14-dn">−0.50%</td><td>890</td><td>$60,031</td><td class="font-semibold text-tdl14-up">+$4,010</td><td>12.1%</td><td>1.44</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:58%"></i></span></td></tr>
          <tr><th scope="row">QBIT</th><td>$18.92</td><td class="font-semibold text-tdl14-up">+0.87</td><td class="font-semibold text-tdl14-up">+4.82%</td><td>3,300</td><td>$62,436</td><td class="font-semibold text-tdl14-dn">−$1,890</td><td>12.6%</td><td>2.10</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:12%"></i></span></td></tr>
          <tr><th scope="row">KRYP</th><td>$305.14</td><td class="font-semibold text-tdl14-up">+5.22</td><td class="font-semibold text-tdl14-up">+1.74%</td><td>260</td><td>$79,336</td><td class="font-semibold text-tdl14-up">+$18,940</td><td>16.1%</td><td>1.35</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:68%"></i></span></td></tr>
          <tr><th scope="row">LMND</th><td>$52.08</td><td class="font-semibold text-tdl14-dn">−1.14</td><td class="font-semibold text-tdl14-dn">−2.14%</td><td>1,780</td><td>$92,702</td><td class="font-semibold text-tdl14-dn">−$6,410</td><td>18.7%</td><td>1.72</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:22%"></i></span></td></tr>
          <tr><th scope="row">MRVL</th><td>$88.72</td><td class="font-semibold text-tdl14-up">+0.94</td><td class="font-semibold text-tdl14-up">+1.07%</td><td>620</td><td>$55,006</td><td class="font-semibold text-tdl14-up">+$3,140</td><td>11.1%</td><td>1.18</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:54%"></i></span></td></tr>
          <tr><th scope="row">OCTA</th><td>$142.55</td><td class="font-semibold text-tdl14-dn">−2.03</td><td class="font-semibold text-tdl14-dn">−1.40%</td><td>340</td><td>$48,467</td><td class="font-semibold text-tdl14-dn">−$4,220</td><td>9.8%</td><td>1.42</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:38%"></i></span></td></tr>
          <tr><th scope="row">PRSM</th><td>$28.60</td><td class="font-semibold text-tdl14-up">+0.55</td><td class="font-semibold text-tdl14-up">+1.96%</td><td>2,200</td><td>$62,920</td><td class="font-semibold text-tdl14-up">+$5,610</td><td>12.7%</td><td>1.61</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:61%"></i></span></td></tr>
          <tr><th scope="row">QSAT</th><td>$71.32</td><td class="font-semibold text-tdl14-up">+1.87</td><td class="font-semibold text-tdl14-up">+2.69%</td><td>520</td><td>$37,086</td><td class="font-semibold text-tdl14-up">+$2,740</td><td>7.5%</td><td>1.09</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:48%"></i></span></td></tr>
          <tr><th scope="row">RVLR</th><td>$14.02</td><td class="font-semibold text-tdl14-dn">−0.28</td><td class="font-semibold text-tdl14-dn">−1.96%</td><td>4,400</td><td>$61,688</td><td class="font-semibold text-tdl14-dn">−$3,120</td><td>12.4%</td><td>1.88</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:19%"></i></span></td></tr>
          <tr><th scope="row">SNTX</th><td>$96.44</td><td class="font-semibold text-tdl14-up">+1.31</td><td class="font-semibold text-tdl14-up">+1.38%</td><td>480</td><td>$46,291</td><td class="font-semibold text-tdl14-up">+$6,830</td><td>9.3%</td><td>1.14</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:76%"></i></span></td></tr>
          <tr><th scope="row">TRVN</th><td>$213.89</td><td class="font-semibold text-tdl14-dn">−1.72</td><td class="font-semibold text-tdl14-dn">−0.80%</td><td>210</td><td>$44,917</td><td class="font-semibold text-tdl14-up">+$1,940</td><td>9.1%</td><td>0.98</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:52%"></i></span></td></tr>
          <tr><th scope="row">UBQT</th><td>$8.44</td><td class="font-semibold text-tdl14-up">+0.36</td><td class="font-semibold text-tdl14-up">+4.45%</td><td>5,600</td><td>$47,264</td><td class="font-semibold text-tdl14-dn">−$1,120</td><td>9.6%</td><td>2.24</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:15%"></i></span></td></tr>
          <tr><th scope="row">VEDA</th><td>$164.28</td><td class="font-semibold text-tdl14-up">+2.41</td><td class="font-semibold text-tdl14-up">+1.49%</td><td>390</td><td>$64,069</td><td class="font-semibold text-tdl14-up">+$7,420</td><td>13.0%</td><td>1.06</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:82%"></i></span></td></tr>
          <tr><th scope="row">WLKN</th><td>$45.66</td><td class="font-semibold text-tdl14-dn">−0.61</td><td class="font-semibold text-tdl14-dn">−1.32%</td><td>1,320</td><td>$60,271</td><td class="font-semibold text-tdl14-dn">−$2,290</td><td>12.2%</td><td>1.51</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:34%"></i></span></td></tr>
          <tr><th scope="row">XGRN</th><td>$29.85</td><td class="font-semibold text-tdl14-up">+0.72</td><td class="font-semibold text-tdl14-up">+2.47%</td><td>2,050</td><td>$61,193</td><td class="font-semibold text-tdl14-up">+$4,010</td><td>12.4%</td><td>1.29</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:58%"></i></span></td></tr>
          <tr><th scope="row">YOTA</th><td>$118.03</td><td class="font-semibold text-tdl14-up">+1.55</td><td class="font-semibold text-tdl14-up">+1.33%</td><td>470</td><td>$55,474</td><td class="font-semibold text-tdl14-up">+$3,820</td><td>11.2%</td><td>0.87</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:67%"></i></span></td></tr>
          <tr><th scope="row">ZPHR</th><td>$78.20</td><td class="font-semibold text-tdl14-dn">−0.98</td><td class="font-semibold text-tdl14-dn">−1.24%</td><td>710</td><td>$55,522</td><td class="font-semibold text-tdl14-dn">−$1,540</td><td>11.2%</td><td>1.36</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:41%"></i></span></td></tr>
          <tr><th scope="row">AXNT</th><td>$36.14</td><td class="font-semibold text-tdl14-up">+0.44</td><td class="font-semibold text-tdl14-up">+1.23%</td><td>1,850</td><td>$66,859</td><td class="font-semibold text-tdl14-up">+$5,110</td><td>13.5%</td><td>1.17</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:55%"></i></span></td></tr>
          <tr><th scope="row">BLTZ</th><td>$188.66</td><td class="font-semibold text-tdl14-up">+3.12</td><td class="font-semibold text-tdl14-up">+1.68%</td><td>300</td><td>$56,598</td><td class="font-semibold text-tdl14-up">+$9,210</td><td>11.5%</td><td>1.22</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:73%"></i></span></td></tr>
          <tr><th scope="row">CVGT</th><td>$52.88</td><td class="font-semibold text-tdl14-dn">−0.71</td><td class="font-semibold text-tdl14-dn">−1.32%</td><td>1,140</td><td>$60,283</td><td class="font-semibold text-tdl14-dn">−$2,780</td><td>12.2%</td><td>1.44</td><td><span class="relative inline-block h-1.5 w-30 rounded-full bg-tdl14-track align-middle before:absolute before:top-1/2 before:left-0 before:h-2.5 before:w-px before:-translate-y-1/2 before:bg-tdl14-mut/50 before:content-[''] after:absolute after:top-1/2 after:right-0 after:h-2.5 after:w-px after:-translate-y-1/2 after:bg-tdl14-mut/50 after:content-['']"><i class="absolute top-1/2 left-(--p) size-2.5 -translate-1/2 rounded-full border-2 border-tdl14-panel bg-tdl14-acc shadow-[0_1px_3px_oklch(0_0_0/0.3)]" style="--p:29%"></i></span></td></tr>
        </tbody>
        <tfoot><tr><th scope="row">Total</th><td></td><td></td><td></td><td></td><td>$1,550,565</td><td class="text-tdl14-up">+$82,905</td><td></td><td></td><td></td></tr></tfoot>
      </table>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tdl14-bg: #f6f7f9;
  --color-tdl14-bg: oklch(0.975 0.004 250);
  --color-tdl14-panel: #ffffff;
  --color-tdl14-panel: oklch(1 0 0);
  --color-tdl14-zebra: #fafbfc;
  --color-tdl14-zebra: oklch(0.987 0.003 250);
  --color-tdl14-hover: #eef3fa;
  --color-tdl14-hover: oklch(0.96 0.015 250);
  --color-tdl14-foot: #f0f3f7;
  --color-tdl14-foot: oklch(0.962 0.008 250);
  --color-tdl14-edge: #dfe3e9;
  --color-tdl14-edge: oklch(0.905 0.008 250);
  --color-tdl14-ink: #232933;
  --color-tdl14-ink: oklch(0.26 0.015 250);
  --color-tdl14-mut: #59606c;
  --color-tdl14-mut: oklch(0.48 0.015 250);
  --color-tdl14-acc: #2a59a6;
  --color-tdl14-acc: oklch(0.48 0.13 255);
  --color-tdl14-up: #157a45;
  --color-tdl14-up: oklch(0.5 0.12 155);
  --color-tdl14-dn: #c0352b;
  --color-tdl14-dn: oklch(0.54 0.18 27);
  --color-tdl14-track: #e6e9ee;
  --color-tdl14-track: oklch(0.925 0.008 250);
  --color-tdl14-shadow: #3c4350;
  --color-tdl14-shadow: oklch(0.3 0.02 250 / 0.25);
  --font-tdl14-sans: 'Red Hat Text', system-ui, sans-serif;
  --font-tdl14-mono: 'Red Hat Mono', ui-monospace, monospace;
}

.tdl-14 {
  width: 100%;
  min-height: 100svh;
}

@media (prefers-reduced-motion: reduce) {
  .tdl-14 * {
    transition: none !important;
  }
}
```

How this works

Sticky cells, one scroller. The wrapper is the only overflow-auto element, so every sticky cell sticks to it. Header cells stick to the top, row headers to the left, footer cells to the bottom.

<table class="border-separate border-spacing-0
  [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-20
  [&_tbody_th]:sticky [&_tbody_th]:left-0 [&_tbody_th]:z-10
  [&_thead_th:first-child]:left-0 [&_thead_th:first-child]:z-30">

The z-index ladder. Row headers (10) slide under column headers (20); the corner (30) and the totals-row corner sit above both. Every sticky cell needs an opaque background or content shows through.

Density from one checkbox. The panel is the :has() host: has-[#tdl14-dense:checked]:[&_td]:py-1.5 re-pads every body cell at once.

Make it yours

  • Freeze two columns: give the second column sticky left-[6rem] (the first column's width) and a z-index of 10.
  • Row height: the default and compact paddings are py-2.5 and py-1.5 on the table's [&_td]: variants.
  • Up/down colours: --color-tdl14-up and --color-tdl14-dn.
  • Remove the totals row: delete <tfoot> — nothing else depends on it.

Gotchas — read before shipping

  • border-collapse breaks sticky borders in every engine; use border-separate border-spacing-0 and draw cell borders with border-b.
  • Any ancestor with overflow:hidden between the cells and the scroller silently disables sticky.
  • Without a background, sticky cells are transparent and scrolled content shows through them.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Tailwind v4 floor; sticky table cells are universally supported.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…