78 UI Kit
Reference

Adapters

Tables and charts are most of a dashboard, and almost no component library themes them. These adapters map three widely-used libraries onto the kit's tokens, so they follow your theme — including when it switches.

Everything below is live
The table, the calendar and the charts on this page are real instances of the real libraries. Flip the theme toggle in the topbar and watch all three follow.

The idea

Your data grid ships its own gray. Your calendar ships its own blue. Drop either into a themed app and it looks bolted on — and if your app has a dark mode, it looks broken. The usual fix is a pile of overrides you maintain forever.

Instead, three rules:

  • Nothing is vendored. The kit ships no copy of any library. You bring your own, at your own version, from wherever you already get it.
  • Adapters are opt-in. They are deliberately not part of kit.css. Include only the ones you actually use.
  • Always after the library. An adapter's job is to win, so it has to come second.

Two of the three are pure CSS and cost you nothing at runtime — the library's own colors resolve to kit tokens, so a theme switch is free. Chart.js is the exception, and the reason is worth knowing: a chart is a <canvas>, and canvas pixels cannot resolve var(--accent). Its adapter is JavaScript, and it does the part everyone misses — see below.

Including them

Library first, then its adapter. Include only the rows you need.

the include pattern
<!-- the kit -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/css/kit.css">
<script defer src="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/js/_78.js"></script>

<!-- Tabulator, then its adapter (verified against Tabulator 6.5.2) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabulator-tables@6.5.2/dist/css/tabulator.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/css/adapters/tabulator.css">
<script defer src="https://cdn.jsdelivr.net/npm/tabulator-tables@6.5.2/dist/js/tabulator.min.js"></script>

<!-- FullCalendar v7 (core + classic theme), then its adapter (verified against 7.0.2) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@7.0.2/skeleton.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@7.0.2/themes/classic/theme.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@7.0.2/themes/classic/palette.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/css/adapters/fullcalendar.css">
<script defer src="https://cdn.jsdelivr.net/npm/fullcalendar@7.0.2/all/global.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/fullcalendar@7.0.2/themes/classic/global.js"></script>

<!-- Chart.js, then its adapter (verified against 4.5.1); the adapter goes after _78.js too -->
<script defer src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.js"></script>
<script defer src="https://cdn.jsdelivr.net/gh/1978io/78UIKit@v0.4.0/js/adapters/chartjs.js"></script>

That is exactly what this page loads — view source and compare. The CSS adapters are version-sensitive, because class names move between majors; the version each was built against is noted at the top of the adapter file and repeated below.

Tabulator CSS adapter

Headers, rows, hover, selection, sort arrows, header filters, pagination and popups all map to kit tokens. Click a row to select it, sort a column, page through, or filter by region — every one of those states is themed.

Built against 6.x, verified on 6.5.2. Header background maps to --bg2, row hover to --row-hover, selection to --row-selected and pagination to --accent. Re-themes for free.

A date range and filters above the grid? Wrap it in the kit's table card and toolbar.

Responsive collapse

With responsiveLayout: "collapse", columns that no longer fit fold into a panel under each row, opened with the + toggle. The adapter themes that panel too. Narrow the window, or open this page on a phone, and open a row.

  • Long values wrap inside the table. Tabulator's cells are nowrap, and the panel would otherwise inherit that and run off the edge. The adapter sets white-space: normal on the panel's cells.
  • The toggle follows the accent. Tabulator draws the +/− with a hardcoded white stroke, which disappears on a pale tint in light theme. The adapter strokes it with currentColor, so it is --accent on --accent-lo in both themes.
  • The panel's type follows the tokens. Tabulator sets the panel to a fixed 14px; the adapter sets --fs-base, the same size as the cells, so a changed type scale reaches it.
responsive collapse
new Tabulator('#accounts', {
  layout: 'fitColumns',
  responsiveLayout: 'collapse',
  responsiveLayoutCollapseStartOpen: false,
  columns: [
    // The toggle column first, so it never collapses itself.
    { formatter: 'responsiveCollapse', width: 40, minWidth: 40, hozAlign: 'center',
      resizable: false, headerSort: false },
    { title: 'Customer', field: 'customer', minWidth: 120, responsive: 0 }, // never collapses
    { title: 'Plan',     field: 'plan',     minWidth: 100, responsive: 1 },
    { title: 'MRR',      field: 'mrr',      minWidth: 90,  responsive: 2 },
    { title: 'Notes',    field: 'notes',    minWidth: 260, responsive: 3 }  // collapses first
  ]
});

FullCalendar CSS adapter

Toolbar, grid lines, today's cell, events, the list view and the focus ring all follow. Switch views to see it hold up.

Primary target v7 core, verified on 7.0.2; a legacy block in the same file keeps v6.x working. Core only — no paid plugins. v7 ships hashed class names, so its palette custom properties are the entire theming surface, and remapping --fc-classic-* onto kit tokens is all the adapter does.

Chart.js JS adapter

Canvas cannot read CSS variables, so this adapter reads the tokens into Chart.defaults instead. And because those defaults are only read when a chart is constructed, it also calls update() on every live chart on _78:themechange — which is the part that makes a theme switch actually work rather than only the first paint.

Everything the theme colors follows a live switch: the datasets the adapter owns, the legend, the tooltip, and the grid, axis line and tick labels. (Chart.js binds each scale's colors once, so the adapter rewrites them on the live scales — but only colors that still match the previous theme's tokens, never ones a chart set for itself.) Scales fixed in 0.3.0

Don't set theme colors per chart
A color you pass in a chart's own options — plugins.legend.labels.color, scales.x.grid.color — out-ranks the adapter and is read once, so it stays on the theme the page loaded in. Leave those unset and the adapter keeps them in step. If you need a dimmer legend on one chart, that is the case for setting it yourself, and for re-setting it on _78:themechange.
Revenue line
Plan mix doughnut

The series colors

An adopted dataset takes its color from its position. The first is --accent; the second is --series-2, a true gray that recedes behind the accent and, having no hue, can never sit near it whatever your accent is — that is the Target line above. From the third on, the six categorical tones take over. The semantic colors are deliberately not in the ramp: a series is a kind of thing, not a state, and a green line reads as “good”.

Changed in 0.3.0
The ramp used to be accent, success, warn, danger, info, dim, so on an existing chart the second and later series change color after upgrading (the first stays the accent). To keep a series' old color, set its borderColor / backgroundColor yourself — the adapter never touches a dataset that declares its own.

Who owns a dataset's color

There is one rule, and it is deliberately simple. A dataset that declares no colors is adopted by the adapter and recolored on every theme switch. A dataset that declares its own backgroundColor or borderColor is never touched — your color wins, permanently. The chart below sets its own purple, so it stays purple in both themes while the axes, grid, legend and tooltip still follow along.

Net new customers explicit color — adapter leaves it alone
dataset ownership
// No colors given → adopted by the adapter, recolored on every theme switch.
new Chart(el, {
  type: 'line',
  data: { labels: months, datasets: [{ label: 'Revenue', data: revenue, fill: true }] }
});

// Explicit color → the adapter never touches this dataset.
new Chart(el, {
  type: 'bar',
  data: { labels: months, datasets: [{ data: net, backgroundColor: '#8b5cf6' }] }
});

// Series colors from the current theme: --accent, --series-2, then --cat-1 … --cat-6.
_78.adapters.chartjs.palette(4);

There is an explicit opt-out for the awkward middle case: a dataset that declares no colors, but that you still want left alone — because a plugin colors it, or you set the colors later. Set $_78 on the dataset and the adapter honors it either way.

the $_78 opt-out
// Hands off, even though this dataset declares no colors of its own.
{ label: 'Forecast', data: forecast, $_78: false }

// Adopt this one, even though it does declare colors.
{ label: 'Actual', data: actual, backgroundColor: '#888', $_78: true }

Left unset, the adapter decides once on first pass — adopted when both backgroundColor and borderColor are undefined — and records its decision on the dataset as $_78.

Built against 4.x, verified on 4.5.1. The adapter also exposes .tokens(), .alpha(color, a) and .apply() for anything it does not cover.