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:
Set
rowHeighton<SvGrid>. Numeric, in pixels. Drives the row height and the active-cell hit box.<SvGrid {data} {columns} features={features} rowHeight={28} />Override
--sg-row-heighton 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).