# Wide grids

> Render only the columns in view for grids with dozens or hundreds of columns, and highlight the column under the pointer.

Rows are always virtualized: only the rows in view are drawn. For grids with many columns (a price history by day, a risk matrix, a wide export), `virtualizeColumns` does the same across: only the columns in the horizontal scroll window, plus a small buffer, are drawn, so a grid of a hundred columns costs about as much as one of ten.

41 columns, of which only the visible ones render. Scroll sideways; the Symbol column stays pinned, and the column under the pointer lights up.

Example: Column virtualization (wide-grids.svelte)

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

  // 40 trading days of closes per symbol: 41 columns, only the visible ones render.
  const DAYS = 40;
  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', width: 100, pinned: 'left' },
    ...Array.from({ length: DAYS }, (_, d): ColumnDef => ({ type: 'price', key: `d${d}`, header: `Day ${d + 1}`, width: 88 })),
  ];
  let rows = $state(
    quotes().map((q) => {
      const row: Record<string, unknown> = { id: q.id, symbol: q.symbol };
      for (let d = 0; d < DAYS; d++) row[`d${d}`] = +(q.price * (1 + Math.sin((d + q.id) / 4) * 0.04)).toFixed(2);
      return row as { id: number };
    }),
  );
</script>

<Grid {rows} {columns} height={300} virtualizeColumns columnHover />
```

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 }));
}
```

## Turning it on

```svelte
<Grid {rows} {columns} height={520} virtualizeColumns />
```

- Columns get pixel widths and the grid scrolls horizontally; `flex` columns grow to fill any width left over.
- Pinned columns always render, so a label column stays put on the left (or right). See [Pinned columns](https://bonguynvan.github.io/bo-grid/docs/pinned-columns/).
- `colSpan` is ignored while columns are virtualized: every cell draws in its own column.

Without `virtualizeColumns`, columns fit the grid's width and every column renders, which is the better choice up to a few dozen columns.

## Following a column

On a wide board, `columnHover` highlights the column under the pointer, body and header, so a value is easy to follow down its column. It draws one overlay for the whole column rather than restyling each cell, and covers pinned columns too. Its colour is the theme token `colHover`.

```svelte
<Grid {rows} {columns} height={520} virtualizeColumns columnHover />
```

## Sixty columns

A wide board with a pinned label column:

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

## Reference

- `virtualizeColumns`: `boolean` (default `false`) — Render only the columns within the horizontal scroll window (+ overscan) for very wide grids (100+ columns). Forces fixed-width horizontal scroll; pinned columns always render. Off, the columns fit the grid's width.
- `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.

- `pinned`: `boolean | 'left' | 'right'` (default `false`) — Pin this column so it stays visible during horizontal scroll. `true` / `'left'` pins to the left edge, `'right'` to the right.
- `width`: `number` (default `96`) — Width in px. A `flex` column starts from it where columns have fixed widths (pinned columns, `virtualizeColumns`), and `fitColumns` scales columns in proportion to it.
- `colSpan`: `(row: GridRow) => number` — How many columns this cell covers for a given row (1 when unset). Covered cells are not drawn. Clamped at a pinned/scrolling boundary and the last column; the leftmost claim wins. Ignored under `virtualizeColumns`.

  ```ts
  { type: 'text', key: 'sym', header: 'Symbol', colSpan: (row) => (row.kind === 'note' ? 4 : 1) }
  ```

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