78 UI Kit
Components

Table

A plain <table> style for static or simple data. For a real data grid — sorting, filtering, paging, editing — use Tabulator and its adapter; this does not try to replace it.

The table

_78-table on the table, wrapped in _78-table-wrap so it scrolls inside its own box on a narrow screen instead of stretching the page. The wrapper is not optional in a responsive layout — without it a wide table pushes the whole document sideways.

Customers by monthly recurring revenue
CustomerPlanSeats MRRHealth
Northwind Team 48 $1,440 92
Contoso Enterprise 210 $8,900 61
Fabrikam Starter 6 $90 18
Total$10,430
table
<div class="_78-table-wrap">
  <table class="_78-table">
    <caption>Customers by monthly recurring revenue</caption>
    <thead>
      <tr>
        <th>Customer</th><th>Plan</th><th class="_78-right">Seats</th>
        <th class="_78-right">MRR</th><th class="_78-center">Health</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="_78-fw-600">Northwind</td>
        <td><span class="_78-badge _78-badge-success">Team</span></td>
        <td class="_78-right">48</td>
        <td class="_78-right _78-text-success">$1,440</td>
        <td class="_78-center"><span class="_78-score-pill _78-high">92</span></td>
      </tr>
      <tr class="_78-selected">
        <td class="_78-fw-600">Contoso</td>
        <td><span class="_78-badge _78-badge-accent">Enterprise</span></td>
        <td class="_78-right">210</td>
        <td class="_78-right _78-text-success">$8,900</td>
        <td class="_78-center"><span class="_78-score-pill _78-mid">61</span></td>
      </tr>
      <tr>
        <td class="_78-fw-600">Fabrikam</td>
        <td><span class="_78-badge _78-badge-dim">Starter</span></td>
        <td class="_78-right">6</td>
        <td class="_78-right">$90</td>
        <td class="_78-center"><span class="_78-score-pill _78-low">18</span></td>
      </tr>
    </tbody>
    <tfoot>
      <tr><td colspan="3">Total</td><td class="_78-right">$10,430</td><td></td></tr>
    </tfoot>
  </table>
</div>

Parts and helpers

Class or elementWhat it does
_78-table-wrapScroll container. Also insets the first column so text lines up with the surrounding card.
<caption>Styled as a small dim heading above the table. Give every table one — add _78-sr-only to keep it for screen readers only.
<tfoot>Totals row: a top border and heavier weight, no hover.
_78-rightRight-aligns a cell and switches on tabular figures, so columns of numbers line up.
_78-centerCenter-aligns a cell. Works on th and td.
_78-selectedOn a <tr> — paints the row with --row-selected.
_78-right brings font-variant-numeric: tabular-nums with it, so you do not also need _78-nums on a right-aligned number column.

Variants

All three combine freely with each other and with the wrapper.

ClassEffect
_78-table-compactTighter cell padding, for dense lists.
_78-table-stripedEven rows get --bg3; hover still wins.
_78-table-stickyHeader sticks while the body scrolls. Needs a scroll container with a height.
Recent activity
EventActorTime
Workspace createdjordan09:31:04
Member invitedjordan09:31:07
Plan upgradedsam09:31:12
Billing updatedsam10:02:55
compact + striped
<div class="_78-table-wrap">
  <table class="_78-table _78-table-compact _78-table-striped">
    <caption class="_78-sr-only">Recent activity</caption>
    <thead><tr><th>Event</th><th>Actor</th><th class="_78-right">Time</th></tr></thead>
    <tbody>
      <tr><td>Workspace created</td><td class="_78-mono">jordan</td><td class="_78-right _78-nums">09:31:04</td></tr>
      <tr><td>Member invited</td><td class="_78-mono">jordan</td><td class="_78-right _78-nums">09:31:07</td></tr>
      <tr><td>Plan upgraded</td><td class="_78-mono">sam</td><td class="_78-right _78-nums">09:31:12</td></tr>
      <tr><td>Billing updated</td><td class="_78-mono">sam</td><td class="_78-right _78-nums">10:02:55</td></tr>
    </tbody>
  </table>
</div>

Sticky header

The header pins itself to the top of the scroll container, so give the wrapper a max-height. Without one there is nothing to scroll and nothing to stick to.

RegionAccounts
North America1,204
Europe986
Asia Pacific655
South America312
Africa188
Middle East141
sticky header
<div class="_78-table-wrap" style="max-height:180px">
  <table class="_78-table _78-table-compact _78-table-sticky">
    <thead><tr><th>Region</th><th class="_78-right">Accounts</th></tr></thead>
    <tbody>
      <tr><td>North America</td><td class="_78-right">1,204</td></tr>
      <tr><td>Europe</td><td class="_78-right">986</td></tr>
      <tr><td>Asia Pacific</td><td class="_78-right">655</td></tr>
      <tr><td>South America</td><td class="_78-right">312</td></tr>
      <tr><td>Africa</td><td class="_78-right">188</td></tr>
      <tr><td>Middle East</td><td class="_78-right">141</td></tr>
    </tbody>
  </table>
</div>

Table card and toolbar

Most data tables end up in the same frame: a card, and above the table a row of filters — a date range, a checkbox or two, how many rows are showing, a way to clear it all. _78-table-card and _78-table-filters are that frame, so the second table that needs it reuses the classes rather than copying the first one's CSS. It works the same over a plain _78-table or a Tabulator grid.

Showing 4 of 4
WhenEventRows
Oct 3Nightly import1,204
Oct 3Error Webhook timed out0
Oct 2Nightly import986
Oct 2Error Rate limited0
_78-table-card with a toolbar
<div class="_78-table-card">
  <div class="_78-table-filters">
    <div class="_78-field _78-table-dates">
      <label for="log-from">From</label>
      <input class="_78-input" id="log-from" type="date">
    </div>
    <div class="_78-field _78-table-dates">
      <label for="log-to">To</label>
      <input class="_78-input" id="log-to" type="date">
    </div>
    <label class="_78-check">
      <input type="checkbox"> Errors only
      <span class="_78-badge _78-badge-danger">2</span>
    </label>
    <span class="_78-grow"></span>
    <span class="_78-table-count">Showing 4 of 4</span>
    <button class="_78-btn _78-btn-ghost _78-btn-sm" type="button">Clear filters</button>
  </div>
  <div class="_78-table-wrap">
    <table class="_78-table _78-table-compact">
      <thead><tr><th>When</th><th>Event</th><th class="_78-right">Rows</th></tr></thead>
      <tbody>
        <tr><td>Oct 3</td><td>Nightly import</td><td class="_78-right">1,204</td></tr>
        <tr><td>Oct 3</td><td><span class="_78-badge _78-badge-danger">Error</span> Webhook timed out</td><td class="_78-right">0</td></tr>
        <tr><td>Oct 2</td><td>Nightly import</td><td class="_78-right">986</td></tr>
        <tr><td>Oct 2</td><td><span class="_78-badge _78-badge-danger">Error</span> Rate limited</td><td class="_78-right">0</td></tr>
      </tbody>
    </table>
  </div>
</div>

The toolbar wraps on a phone, and a _78-table-dates pair stays side by side. Labeled fields line up by their bottoms; a checkbox, a count or a small button centers on the inputs' line. A _78-grow spacer pushes the count and the clear button to the right.

Over a Tabulator

Put the Tabulator element where the _78-table-wrap was, and let Tabulator's own setFilter do the filtering. Hide the clear button with hidden until a filter is set:

toolbar over a Tabulator
<div class="_78-table-card">
  <div class="_78-table-filters"> … the same toolbar … </div>
  <div id="activity"></div>
</div>

<script>
var table = new Tabulator('#activity', { data: rows, layout: 'fitColumns', columns: [ … ] });
function apply() {
  table.setFilter(function (r) {
    return (!from.value || r.date >= from.value) && (!to.value || r.date <= to.value)
        && (!errorsOnly.checked || r.level === 'error');
  });
}
table.on('dataFiltered', function (filters, shown) {
  count.textContent = 'Showing ' + shown.length + ' of ' + rows.length;
});
</script>
ClassWhat it does
_78-table-cardThe card: a card surface with tighter --space-3 padding.
_78-table-filtersThe toolbar: a wrapping row, bottoms aligned, --space-3 above the table. Fields lose their bottom margin.
_78-table-datesOn a _78-field with a date input: 130–170px wide, so a From / To pair stays side by side on a phone.
_78-table-countThe “Showing 12 of 40” readout: small, dim, tabular figures.