Get started
Quick start
Install bo-grid and render a first grid in Svelte, React, Vue or plain JavaScript.
Install
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.
<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} />The same grid, running: sort a column from its header, resize one from its edge, or select a range to see its sum.
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).
Example
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.
Example
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).
Example
<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.
<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} />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: props, the API on a ref, and server rendering.
- Price board: a full board under a busy feed, live.
- Examples: every feature, live, with its source.