# Quick start

> Install bo-grid and render a first grid in Svelte, React, Vue or plain JavaScript.

## Install

```sh
npm i bo-grid
```

Svelte 5 is a peer dependency. React and Vue are optional peers, needed only for `bo-grid/react` and `bo-grid/vue`.

## A first grid

Give the grid rows (each with an `id`) and columns. A column's `type` picks how its values show: `price`, `percent`, `volume`, `date`, `heatmap`, `sparkline` and more. The tabs follow the framework chosen in the top bar.

Svelte:

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

  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol' },
    { type: 'price', key: 'price', header: 'Price', flash: 'auto' },
    { type: 'percent', key: 'change', header: 'Chg %' },
    { type: 'volume', key: 'volume', header: 'Volume' },
  ];
  let rows = $state([
    { id: 1, symbol: 'VNM', price: 68.5, change: 1.2, volume: 1_204_000 },
    { id: 2, symbol: 'FPT', price: 132, change: -0.4, volume: 860_500 },
  ]);
</script>

<Grid {rows} {columns} height={400} />
```

React:

```tsx
import { useState } from 'react';
import { BoGrid } from 'bo-grid/react';
import type { ColumnDef } from 'bo-grid';

const columns: ColumnDef[] = [
  { type: 'text', key: 'symbol', header: 'Symbol' },
  { type: 'price', key: 'price', header: 'Price', flash: 'auto' },
  { type: 'percent', key: 'change', header: 'Chg %' },
  { type: 'volume', key: 'volume', header: 'Volume' },
];

export function Board() {
  const [rows] = useState([
    { id: 1, symbol: 'VNM', price: 68.5, change: 1.2, volume: 1_204_000 },
    { id: 2, symbol: 'FPT', price: 132, change: -0.4, volume: 860_500 },
  ]);
  return <BoGrid rows={rows} columns={columns} height={400} />;
}
```

Vue:

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { BoGrid } from 'bo-grid/vue';
import type { ColumnDef } from 'bo-grid';

const columns: ColumnDef[] = [
  { type: 'text', key: 'symbol', header: 'Symbol' },
  { type: 'price', key: 'price', header: 'Price', flash: 'auto' },
  { type: 'percent', key: 'change', header: 'Chg %' },
  { type: 'volume', key: 'volume', header: 'Volume' },
];
const rows = ref([
  { id: 1, symbol: 'VNM', price: 68.5, change: 1.2, volume: 1_204_000 },
  { id: 2, symbol: 'FPT', price: 132, change: -0.4, volume: 860_500 },
]);
</script>

<template>
  <BoGrid :rows="rows" :columns="columns" :height="400" />
</template>
```

JavaScript:

```js
import { createBoGrid } from 'bo-grid/element';

/** @type {import('bo-grid').ColumnDef[]} */
const columns = [
  { type: 'text', key: 'symbol', header: 'Symbol' },
  { type: 'price', key: 'price', header: 'Price', flash: 'auto' },
  { type: 'percent', key: 'change', header: 'Chg %' },
  { type: 'volume', key: 'volume', header: 'Volume' },
];
const rows = [
  { id: 1, symbol: 'VNM', price: 68.5, change: 1.2, volume: 1_204_000 },
  { id: 2, symbol: 'FPT', price: 132, change: -0.4, volume: 860_500 },
];

const config = { columns, rows, height: 400 };
const grid = createBoGrid(config);
document.querySelector('#board')?.append(grid);
// Later: grid.config = { ...config, rows: nextRows };
```

The same grid, running: sort a column from its header, resize one from its edge, or select a range to see its sum.

Example: A first grid (quick-start.svelte)

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

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

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

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

The three props every grid takes:

- `rows`: `GridRow[]` — The data: one plain object per row, each with a unique `id`, a number or a string (or name another key with `getRowId`).

  ```ts
  let rows: GridRow[] = $state([
    { id: 1, sym: 'VNM', last: 68.5 },
    { id: 2, sym: 'FPT', last: 121.3 },
  ]);
  ```

- `columns`: `ColumnDef[]` — The columns, in display order. A column's `type` picks how its values show, sort and filter.

  ```ts
  const columns: ColumnDef[] = [
    { type: 'text', key: 'sym', header: 'Symbol', width: 90 },
    { type: 'price', key: 'last', header: 'Last', flash: 'auto' },
  ];
  ```

- `height`: `number | string` — Viewport height. A **number** is the scroll viewport's pixel height (the grid's total height is that plus header/toolbar/footer chrome). A **CSS string** (e.g. `'100%'`, `'80vh'`, `'480px'`) sizes the whole grid element and the viewport auto-fits the space left after the chrome — give the grid a sized parent (or use `'100%'` inside a flex/grid cell).

  ```svelte
  <Grid {rows} {columns} height="100%" />
  ```


## A live feed

For a busy feed, keep the rows as plain objects and let the grid write the ticks. `createTickStream` coalesces messages per key and hands the grid one batch a frame; `patchRows` repaints only the cells that changed.

Svelte:

```svelte
<script lang="ts">
  import { Grid, type GridApi } from 'bo-grid';
  import { createTickStream } from 'bo-grid/realtime';

  let rows = $state.raw(initialQuotes); // plain objects: the grid writes the ticks
  let api: GridApi;
  const stream = createTickStream<string, Partial<Quote>>({
    apply: (batch) => api.patchRows(batch), // repaints only the cells that changed
  });
  stream.start();
  socket.onmessage = (e) => {
    const q = JSON.parse(e.data);
    stream.push(q.symbol, q);
  };
</script>

<Grid {rows} {columns} getRowId={(r) => String(r.symbol)} onReady={(a) => (api = a)} height={400} />
```

React:

```tsx
import { useEffect, useRef } from 'react';
import { BoGrid } from 'bo-grid/react';
import { createTickStream } from 'bo-grid/realtime';
import type { GridApi } from 'bo-grid';

export function Board({ initialQuotes }: { initialQuotes: Quote[] }) {
  const grid = useRef<GridApi>(null);
  useEffect(() => {
    const stream = createTickStream<string, Partial<Quote>>({
      apply: (batch) => grid.current?.patchRows(batch),
    });
    stream.start();
    socket.onmessage = (e) => {
      const q = JSON.parse(e.data);
      stream.push(q.symbol, q);
    };
    return () => stream.stop();
  }, []);
  return <BoGrid ref={grid} rows={initialQuotes} columns={columns} getRowId={(r) => String(r.symbol)} height={400} />;
}
```

Vue:

```vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { BoGrid } from 'bo-grid/vue';
import { createTickStream } from 'bo-grid/realtime';

const grid = ref<InstanceType<typeof BoGrid>>();
const stream = createTickStream<string, Partial<Quote>>({
  apply: (batch) => grid.value?.api?.patchRows(batch),
});
onMounted(() => {
  stream.start();
  socket.onmessage = (e) => {
    const q = JSON.parse(e.data);
    stream.push(q.symbol, q);
  };
});
onUnmounted(() => stream.stop());
</script>

<template>
  <BoGrid ref="grid" :rows="initialQuotes" :columns="columns" :get-row-id="(r) => String(r.symbol)" :height="400" />
</template>
```

Past about 10,000 messages a second, read the socket in a worker with `serveFeed` and `connectFeed`: the main thread then takes one batch a frame instead of every message.

## Next

- [React, Vue & more](https://bonguynvan.github.io/bo-grid/docs/frameworks/): props, the API on a ref, and server rendering.
- [Price board](https://bonguynvan.github.io/bo-grid/examples/priceboard/): a full board under a busy feed, live.
- [Examples](https://bonguynvan.github.io/bo-grid/examples/): every feature, live, with its source.

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