# Theming

> Style the grid with a built-in preset, a token map of your own, or CSS variables, from colours to fonts, corner radius and density.

The grid brings its own styles: there is no stylesheet to import. Its look is a set of tokens (colours, fonts, radius, padding), and the `theme` prop sets them from a built-in preset or a map of your own. Every token is also a `--bo-grid-*` CSS variable, so a page stylesheet can set them too.

Switch between compact, default and roomy: density and shape are tokens like the colours.

Example: Density and shape tokens (theming.svelte)

```svelte
<script lang="ts">
  import { Grid, type ColumnDef, type GridTheme } from 'bo-grid';
  import { quotes } from './data';

  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', sub: 'name', width: 170 },
    { type: 'price', key: 'price', header: 'Price' },
    { type: 'percent', key: 'change', header: 'Chg %' },
    { type: 'volume', key: 'volume', header: 'Volume', flex: 1 },
  ];
  let rows = $state(quotes());

  // Density and shape are tokens too. A partial theme keeps every other token.
  const looks: Record<string, { rowHeight: number; theme: GridTheme }> = {
    Compact: { rowHeight: 28, theme: { fontSize: '12px', cellPad: '6px', radius: '4px' } },
    Default: { rowHeight: 36, theme: {} },
    Roomy: { rowHeight: 46, theme: { fontSize: '14px', cellPad: '16px', radius: '14px' } },
  };
  let look = $state('Compact');
</script>

<p style="margin: 0 0 10px; display: flex; gap: 6px">
  {#each Object.keys(looks) as name (name)}
    <button aria-pressed={look === name} onclick={() => (look = name)}>{name}</button>
  {/each}
</p>
<Grid {rows} {columns} height={280} rowHeight={looks[look].rowHeight} theme={looks[look].theme} />
```

data.ts:

```ts
// Small, fixed data for the docs' examples: every example reads the same
// symbols, so a reader compares features, not datasets.
// A type, not an interface: it then fits GridRow's index signature.
export type Quote = {
  id: number;
  symbol: string;
  name: string;
  sector: string;
  price: number;
  change: number;
  volume: number;
};

const BASE: Omit<Quote, 'id'>[] = [
  { symbol: 'VNM', name: 'Vinamilk', sector: 'Consumer', price: 68.5, change: 1.2, volume: 1_204_000 },
  { symbol: 'FPT', name: 'FPT Corp', sector: 'Technology', price: 132, change: -0.4, volume: 860_500 },
  { symbol: 'HPG', name: 'Hoa Phat', sector: 'Materials', price: 27.35, change: 0.74, volume: 3_412_900 },
  { symbol: 'VCB', name: 'Vietcombank', sector: 'Financials', price: 91.5, change: 0.55, volume: 1_986_200 },
  { symbol: 'MWG', name: 'Mobile World', sector: 'Consumer', price: 51.4, change: -1.1, volume: 2_250_400 },
  { symbol: 'SSI', name: 'SSI Securities', sector: 'Financials', price: 33.3, change: -0.45, volume: 4_104_700 },
  { symbol: 'VIC', name: 'Vingroup', sector: 'Real estate', price: 44.15, change: -0.11, volume: 1_530_100 },
  { symbol: 'GAS', name: 'PV Gas', sector: 'Energy', price: 71.8, change: 0.3, volume: 645_800 },
  { symbol: 'MSN', name: 'Masan', sector: 'Consumer', price: 76.2, change: 2.05, volume: 1_120_300 },
  { symbol: 'TCB', name: 'Techcombank', sector: 'Financials', price: 23.9, change: 0.84, volume: 5_870_000 },
  { symbol: 'VHM', name: 'Vinhomes', sector: 'Real estate', price: 41.6, change: -0.72, volume: 2_904_600 },
  { symbol: 'POW', name: 'PV Power', sector: 'Energy', price: 12.25, change: 1.66, volume: 6_315_200 },
];

/** A fresh copy of the quotes, so one example's edits never reach another. */
export function quotes(): Quote[] {
  return BASE.map((q, i) => ({ id: i + 1, ...q }));
}
```

## Presets

`theme` takes a name, `'dark'` (the default) or `'light'`, or a token map. Eight presets are exported as token maps:

| Export | Name in `themePresets` |
| --- | --- |
| `darkTheme` | `dark` |
| `lightTheme` | `light` |
| `highContrastDark` | `high-contrast-dark` |
| `highContrastLight` | `high-contrast-light` |
| `midnightTheme` | `midnight` |
| `terminalTheme` | `terminal` |
| `tradecanvasTheme` | `tradecanvas` |
| `tradecanvasLightTheme` | `tradecanvas-light` |

```svelte
<script lang="ts">
  import { Grid, themePresets, type ThemePreset } from 'bo-grid';
  let preset = $state<ThemePreset>('midnight');
</script>

<Grid {rows} {columns} height={480} theme={themePresets[preset]} />
```

`themePresets` maps each name to its tokens, for a theme picker; `ThemePreset` is the type of the names. The high-contrast presets are tuned well past the WCAG AA contrast ratios.

Live example: https://bonguynvan.github.io/bo-grid/examples/themes/ (source: https://github.com/bonguynvan/bo-grid/blob/main/src/demo/examples/)

## Your own tokens

A token map sets only the tokens you give; the rest keep their defaults:

```svelte
<Grid {rows} {columns} height={480} theme={{ bg: '#0b1020', headerBg: '#0d1226', up: '#22d3ee', down: '#fb7185', selBorder: '#22d3ee' }} />
```

| Token | What it sets |
| --- | --- |
| `bg`, `headerBg` | Grid and header background |
| `rowA`, `rowB`, `rowHover` | Alternating row backgrounds and the hovered row |
| `colHover` | The column highlight under `columnHover` |
| `text`, `textDim`, `border` | Text, secondary text and lines |
| `up`, `down`, `amber` | Rises, falls and warnings (flashes, signed numbers, badges) |
| `selFill`, `selBorder` | The selection's fill and outline |
| `mono`, `sans`, `fontSize` | Number and text fonts, and the cell font size |
| `radius`, `cellPad`, `headerH` | Corner radius, horizontal cell padding, header height |
| `scheme` | `'light'` or `'dark'` for native controls (checkboxes, date pickers, scrollbars) |

Numbers use tabular figures, so digits line up down a column whatever the font.

## Density

Density is two settings: the `rowHeight` prop and the `cellPad` token (with `fontSize` to match):

```svelte
<!-- Compact -->
<Grid {rows} {columns} height={480} rowHeight={28} theme={{ fontSize: '12px', cellPad: '6px' }} />

<!-- Roomy and rounded -->
<Grid {rows} {columns} height={480} rowHeight={44} theme={{ radius: '16px', cellPad: '16px', fontSize: '14px' }} />
```

## CSS variables

Each token is a CSS custom property on the grid, so you can set them from a stylesheet on any ancestor instead of the prop:

```css
.my-app {
  --bo-grid-bg: #ffffff;
  --bo-grid-text: #1a1a1a;
  --bo-grid-up: #16a34a;
  --bo-grid-down: #dc2626;
  --bo-grid-scheme: light;
}
```

The variable names follow the tokens: `bg` is `--bo-grid-bg`, `headerBg` is `--bo-grid-header-bg`, `cellPad` is `--bo-grid-cell-pad`. A `theme` prop sets the same variables on the grid itself, so it wins over an ancestor's. `themeVars(theme)` turns a token map into the variable declarations, for a wrapper of your own.

The `--bo-grid-*` variables are inputs: they hold a value only where you or a `theme` set one. To use a theme colour inside the grid (a custom cell, a `cellClass` style), read the colour the grid resolved, the same name without `grid-`: `var(--bo-up)`, `var(--bo-down)`, `var(--bo-text)`, `var(--bo-text-dim)`, `var(--bo-amber)`. These always hold the current theme's colour.

Native controls inside the grid (checkboxes, date and number inputs, the search box, scrollbars) follow the theme through `color-scheme` and `accent-color`. A custom map is treated as dark unless it sets `scheme: 'light'`.

## Styling rows and cells

For styling that depends on the data, use a column's `cellClass` or the grid's `rowClass`; see [Row styles](https://bonguynvan.github.io/bo-grid/docs/row-styles/) and [Conditional formatting](https://bonguynvan.github.io/bo-grid/docs/conditional-formatting/).

## Reference

- `theme`: `'dark' | 'light' | GridTheme` (default `'dark'`) — Built-in theme name, or a token map (a preset such as `midnightTheme`, or your own, spread over one).

  ```svelte
  <Grid {rows} {columns} height={480} theme={{ ...lightTheme, up: '#0a7f4f' }} />
  ```

- `rowHeight`: `number | ((row: GridRow, index: number) => number)` (default `36`) — Row height in px (uniform), or a function for variable heights (in-memory mode only).

  ```svelte
  <Grid {rows} {columns} height={480} rowHeight={(row) => (row.note ? 56 : 36)} />
  ```

- `columnHover`: `boolean` (default `false`) — Highlight the column under the pointer (body and header) — handy on a wide board. One overlay for the whole column, not a class per cell.

---
Page: https://bonguynvan.github.io/bo-grid/docs/theming/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
