JSFiddle - React, Tailwind, and code Playground

by stitot

HTML

<div>
  <table>
    <caption>
      Table 1: No height (just a regular default table with no extras)
    </caption>
    <thead>
      <tr>
        <th>dessert</th>
        <th>calories</th>
        <th>fat</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="outline">Fruit Salad</td>
        <td>50</td>
        <td>0.3</td>
      </tr>
      <tr>
        <td>Rice Pudding</td>
        <td>118</td>
        <td class="outline">2</td>
      </tr>
      <tr>
        <td>Custard<br />Line two</td>
        <td class="outline">122</td>
        <td>3.5</td>
      </tr>
      <tr>
        <td>Bread Pudding</td>
        <td>153</td>
        <td>4</td>
      </tr>
    </tbody>
  </table>
</div>

<div>
  <table class="scroll">
    <caption>
      Table 2: Height on tbody (add stuff for tbody scroll, but no absolute
      positioning etc.)
    </caption>
    <thead>
      <tr>
        <th>dessert</th>
        <th>calories</th>
        <th>fat</th>
      </tr>
    </thead>
    <tbody style="height: 150px">
      <tr>
        <td class="outline">Fruit Salad</td>
        <td>50</td>
        <td>0.3</td>
      </tr>
      <tr>
        <td>Rice Pudding</td>
        <td>118</td>
        <td class="outline">2</td>
      </tr>
      <tr>
        <td>Custard<br />Line two</td>
        <td class="outline">122</td>
        <td>3.5</td>
      </tr>
      <tr>
        <td>Bread Pudding</td>
        <td>153</td>
        <td>4</td>
      </tr>
      <tr>
        <td class="outline">Fruit Salad</td>
        <td>50</td>
        <td>0.3</td>
      </tr>
      <tr>
        <td>Rice Pudding</td>
        <td>118</td>
        <td class="outline">2</td>
      </tr>
      <tr>
        <td>Custard</td>
        <td class="outsline">122</td>
        <td>3.5</td>
      </tr>
      <tr>
        <td>Bread Pudding</td>
        <td>153</td>
        <td>4</td>
      </tr>
      <tr>
        <td class="outline">Fruit Salad</td>
        <td>50</td>
        <td>0.3</td>
      </tr>
      <tr>
...

CSS

@import url("https://code.highcharts.com/css/highcharts.css"); /* For colors only */

:root {
  /* Fiddle around with these (and hit Run!) to see changes */
  --table-border: 1px;
  --column-border: 1px;
  --row-border: 1px;
  --inset: 1px; /* We recommend inset for hightlight borders to avoid content shifting. */
  --padding: 8px;
  --border-spacing: 0px; /* This is the big one, allowing for more "decorative" tables. */
  --cell-border-radius: 0px; /* Best suited if border-spacing > 0. We need for <table> as well. */
}

div {
  margin: 20px;
}

table {
  box-sizing: border-box;
  width: 100%;
  border-collapse: separate;
  border-spacing: var(--border-spacing);
  font-family: Helvetica, Arial, sans-serif;
  table-layout: fixed;
  color: var(--highcharts-neutral-color-100);
  border: var(--table-border) solid var(--highcharts-neutral-color-20);
}

table thead {
  background-color: var(--highcharts-neutral-color-5);
}

table th,
table td {
  border-radius: var(--cell-border-radius);
  box-sizing: border-box;
  padding: var(--padding);
  border-bottom: var(--row-border, var(--table-border)) solid
    var(--highcharts-neutral-color-20);
  border-right: var(--column-border, var(--table-border)) solid
    var(--highcharts-neutral-color-20);
  border-left: clamp(
      0px,
      var(--border-spacing, 0px),
      var(--column-border, var(--table-border))
    )
    solid var(--highcharts-neutral-color-20);
  border-top: clamp(
      0px,
      var(--border-spacing, 0px),
      var(--row-border, var(--table-border))
    )
    solid var(--highcharts-neutral-color-20);
}

/* The above and below clamp() stuff inserts top and left border only if --border-spacing > 0px, 'cause that looks better. If user disagrees they can override entire selector. */

table tr:last-of-type td {
  border-bottom-width: clamp(
    0px,
    var(--border-spacing, 0px),
    var(--row-border, var(--table-border))
  );
}

table tr th:last-of-type,
table tr td:last-of-type {
  border-right-width:...

JavaScript

document.addEventListener("DOMContentLoaded", function () {
  const tables = document.querySelectorAll("table")

  tables.forEach((table, index) => {
    // This borderSpacing could probably be more slick, but for PoC only
    const computedStyle = window.getComputedStyle(table)
    const borderSpacing = parseFloat(computedStyle.borderSpacing)
    if (borderSpacing) table.classList.add("borderSpacing")

    if (table.querySelector("tbody").style.height != "") enableScroll(table)
    if (table.classList.contains("enableVirtualization"))
      enableVirtualization(table)
  })
})

function enableScroll(table) {
  const computedStyle = window.getComputedStyle(table)
  const tableWidth = parseFloat(computedStyle.width)
  const borderWidth = parseFloat(computedStyle.borderWidth)
  const tbody = table.querySelector("tbody")
  tbody.style.width = `${tableWidth - borderWidth * 2}px`
}

function enableVirtualization(table) {
  table.classList.replace("scroll", "virtual")

  const thead = table.querySelector("thead")
  const tbody = table.querySelector("tbody")
  const rows = tbody.querySelectorAll("tr")
  const ths = thead.querySelectorAll("th")

  const computedStyle = window.getComputedStyle(table)
  const borderSpacing = parseFloat(computedStyle.borderSpacing) || 0

  const thWidths = Array.from(ths)
    .map((th) => th.getBoundingClientRect().width + "px")
    .join(" ")

  let topOffset = 0

  rows.forEach((row) => {
    row.style.cssText = `
            transform: translateY(${topOffset}px);
            grid-template-columns: ${thWidths};
        `
    topOffset += row.getBoundingClientRect().height + borderSpacing
  })
}