Theming Theming Overview

Theming Overview

Three cooperating layers: the prebuilt themes, the override CSS, and the density toggle.

Pretable's theming is built around three cooperating layers. Each owns one thing.

The three layers

┌─────────────────────────────────────┐ │ Your CSS │ Layer 3 (consumer override) │ :root { --pretable-accent: red; } │ Highest specificity (last loaded) └─────────────────────────────────────┘ ▼ overrides ┌─────────────────────────────────────┐ │ @pretable/ui/grid.css │ Layer 2 (chrome) │ [data-pretable-cell] { │ Targets engine attributes │ background: var(--pretable-...); │ │ ... │ │ } │ └─────────────────────────────────────┘ ▼ resolves vars from ┌─────────────────────────────────────┐ │ @pretable/ui/themes/excel.css │ Layer 1 (theme tokens) │ :root { │ │ --pretable-bg-grid: #ffffff; │ │ --pretable-accent: #107C41; │ 24 tokens defined │ ... │ │ } │ └─────────────────────────────────────┘ ▼ writes to ┌─────────────────────────────────────┐ │ @pretable/react │ Engine (no styling) │ <div data-pretable-cell="" /> │ │ Reads --pretable-row-height + │ │ --pretable-header-height for │ │ virtualization math. │ └─────────────────────────────────────┘

Layer 1 — theme tokens. A theme file (themes/excel.css or themes/material.css) declares all 24 --pretable-* tokens at :root. This is the single source of truth for what the grid looks like.

Layer 2 — grid.css. A selector-based stylesheet (grid.css) targets the engine's data attributes ([data-pretable-scroll-viewport], [data-pretable-cell], etc.) and applies var(--pretable-*) references. The engine emits the markup; this layer makes it visible.

Layer 3 — your CSS. Whatever you put in your application's stylesheet, loaded after the imports. CSS cascade specificity: a redefinition at :root in your stylesheet wins over the theme's :root because yours loads later. The override story.

The 24-token contract

Pretable's public token contract has 24 tokens, grouped:

  • Surfaces (5): bg-grid, bg-grid-alt, bg-header, bg-toolbar, bg-tooltip
  • Text (3): text-cell, text-header, text-dim
  • Lines (3): rule, rule-strong, radius
  • State (4): bg-hover, bg-selected, text-selected, focus-ring
  • Accent (1): accent
  • Density (6): row-height, header-height, cell-padding-x, cell-padding-y, font-size-cell, font-size-header
  • Typography (2): font-sans, font-mono

Each prefixed --pretable-. See Token reference for descriptions, types, and example values per theme.

Two of the density tokens (--pretable-row-height and --pretable-header-height) are read by the engine in JavaScript via the useResolvedHeights hook. The other 22 are CSS-only.

The two attribute axes

Two data-* attributes on <html> toggle runtime variants:

  • data-theme="dark" — activates the [data-theme="dark"] block in the active theme file. Material has a dark variant; Excel is light-only.
  • data-density="compact|standard|spacious" — switches density tokens. Each theme defines its own three tiers; the engine reads the new heights via MutationObserver and re-renders.

The two axes compose independently. <html data-theme="dark" data-density="compact"> gives you Material dark in compact density. CSS specificity handles the rest.

Composition order

When the cascade resolves, layers compose in this order:

  1. Theme file's :root block writes initial token values.
  2. Theme file's [data-density] block (if attribute is set) overrides density tokens.
  3. Theme file's [data-theme="dark"] block (Material only, if attribute is set) overrides color tokens.
  4. Your application's :root block (if you redefine any tokens) wins because it loads after the theme.
  5. grid.css reads var(--pretable-*) references and applies them to the engine's data-attribute selectors.

You can mix any of these — for example, swap to Material dark and override only the accent color:

css
@import "@pretable/ui/themes/material.css"; @import "@pretable/ui/grid.css"; :root { --pretable-accent: #ff5722; }
html
<html data-theme="dark"> ... </html>

The accent override applies in both light and dark mode (because it's at :root, outside the [data-theme="dark"] block).

Where to go next