5. Theme and density

Step 5 of 6 · ← Features · Next: Going to production →

The render component (<SvGrid>) ships its own scoped styles. You re-theme it by declaring --sg-* CSS custom properties at any level above the grid - :root for the whole app, a wrapper <div> for one instance, or directly on the <SvGrid> element itself.

Token surface

The 20-odd tokens the renderer reads:

Token What it paints
--sg-bg Cell background
--sg-fg Cell text
--sg-muted Secondary text (footers, subtitles)
--sg-border Cell + header borders
--sg-header-bg / --sg-header-fg Header row
--sg-row-alt-bg Zebra rows
--sg-row-hover-bg Row + cell hover
--sg-row-height Row height
--sg-selection-bg Selected cell / row tint
--sg-accent Sort indicator, focus ring, primary buttons
--sg-focus-ring Keyboard focus outline
--sg-input-bg / --sg-input-border Inline editor + filter inputs
--sg-pill-active / -fg "Active" status pills
--sg-pill-pending / -fg "Pending" status pills
--sg-pill-inactive / -fg "Inactive" status pills
--sg-scrollbar-* (10 tokens) Custom-painted scrollbars

Light + dark via data-theme

The gallery flips themes by writing dark or light to html[data-theme]. Every token redeclares under that selector:

:root {
  --sg-bg:          #ffffff;
  --sg-fg:          #0f172a;
  --sg-border:      #e2e8f0;
  --sg-header-bg:   #f1f5f9;
  --sg-row-alt-bg:  #f8fafc;
  --sg-row-hover-bg: #eef2ff;
  --sg-accent:      #2563eb;
}

html[data-theme='dark'] {
  --sg-bg:          #0f172a;
  --sg-fg:          #f1f5f9;
  --sg-border:      #334155;
  --sg-header-bg:   #1e2433;
  --sg-row-alt-bg:  #1b2230;
  --sg-row-hover-bg: #232b3c;
  --sg-accent:      #3b82f6;
  color-scheme: dark;
}

Toggling is one line in the app shell:

<script lang="ts">
  let theme = $state<'light' | 'dark'>('dark')
  $effect(() => document.documentElement.setAttribute('data-theme', theme))
</script>

<button onclick={() => (theme = theme === 'dark' ? 'light' : 'dark')}>
  Toggle theme
</button>

Per-instance theming

Because the tokens are plain custom properties they cascade. To style a single grid, wrap it in a <div> that sets its own values:

<div style="--sg-bg: #fff8f0; --sg-accent: #db2777;">
  <SvGrid {data} {columns} features={features} />
</div>

The 10-custom-cells-and-themes demo applies three full palettes (light / dark / high-contrast) this way.

Density

Two ways:

  1. Set rowHeight on <SvGrid>. Numeric, in pixels. Drives the row height and the active-cell hit box.

    <SvGrid {data} {columns} features={features} rowHeight={28} />
    
  2. Override --sg-row-height on a wrapper. Same effect, with the token shape if you'd rather express density in CSS.

    .compact { --sg-row-height: 28px; }
    .comfortable { --sg-row-height: 48px; }
    

A user-facing "density selector" is half a dozen lines:

<script lang="ts">
  let density = $state<'compact' | 'normal' | 'comfortable'>('normal')
  const height = $derived(
    density === 'compact' ? 28 : density === 'comfortable' ? 48 : 36,
  )
</script>

<select bind:value={density}>
  <option value="compact">Compact</option>
  <option value="normal">Normal</option>
  <option value="comfortable">Comfortable</option>
</select>

<SvGrid {data} {columns} features={features} rowHeight={height} />

Sizing the grid

The wrapper renders inside whatever container you give it. The containerHeight prop sets the scrollable shell height:

<!-- Numeric: px -->
<SvGrid {data} {columns} features={features} containerHeight={520} />

<!-- String: passed through to CSS -->
<SvGrid {data} {columns} features={features} containerHeight="100%" />
<SvGrid {data} {columns} features={features} containerHeight="auto" />

For a flex-grow layout the canonical recipe is:

<div class="flex flex-col h-screen">
  <header>…</header>
  <div class="flex-1 min-h-0">
    <SvGrid {data} {columns} features={features} containerHeight="100%" />
  </div>
</div>

The min-h-0 is the bit that bites. Flex children default to min-height: auto, which prevents the inner scroll container from shrinking, which makes the whole page scroll instead of the grid.

Full Tailwind integration

If your app uses Tailwind, see Tailwind integration for: install + PostCSS config, @custom-variant so the dark: modifier follows data-theme, the override hooks for the stable .sv-grid-* class names, and the anti-patterns (don't @apply inside grid selectors, don't put utility classes on grid children, don't fight column widths in CSS).