Get started
React, Vue & more
The grid as a React or a Vue component, the <bo-grid> custom element for Angular and plain JavaScript, and server rendering in each.
bo-grid is a Svelte 5 component. In Svelte, import Grid from bo-grid: it is the smallest build, and the only one that takes Svelte snippets for custom cells. For React and Vue there are typed components, and for everything else a custom element. All three take the same props and expose the same grid API.
| You write | Import | API handle |
|---|---|---|
| Svelte 5 | import { Grid } from 'bo-grid' | onReady |
| React 18 or 19 | import { BoGrid } from 'bo-grid/react' | the component’s ref |
| Vue 3.3+ | import { BoGrid } from 'bo-grid/vue' | the template ref’s api |
| Angular, plain JS | import 'bo-grid/element' | onReady in the element’s config |
Build-free starters for plain JavaScript, React and Vue are in the repository’s examples folder: run npx serve examples and open one.
React
bo-grid/react takes every <Grid> prop as a React prop; its ref is the grid’s API.
import { useEffect, useRef } from 'react';
import { BoGrid } from 'bo-grid/react';
import type { GridApi } from 'bo-grid';
export function Board({ rows }: { rows: Quote[] }) {
const grid = useRef<GridApi>(null);
// A live feed: patch the rows in place, with no React re-render.
useEffect(() => feed.subscribe((ticks) => grid.current?.patchRows(ticks)), []);
return <BoGrid ref={grid} rows={rows} columns={columns} height={520} onCellEdit={save} className="board" />;
} - It loads the
<bo-grid>element on the client, so server rendering (Next.js, Remix) renders its wrapper div and nothing else, and hydration matches. The bundle carries'use client'for server components. classNameandstylego on that wrapper div, which keeps the grid’sheightuntil the element loads, so nothing jumps.- Each render sets the element’s config from the props. Unchanged arrays and functions are not reprocessed, but a new
rowsarray is a new view: for a live feed, patch through the ref (patchRows,flashCells) instead of passing new rows.
Vue
bo-grid/vue takes every <Grid> prop as an attribute (:rows, :row-height, a bare footer) and every callback as an event (@cell-edit is onCellEdit); the template ref’s api is the grid’s API.
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { BoGrid } from 'bo-grid/vue';
const grid = ref<InstanceType<typeof BoGrid>>();
// A live feed: patch the rows in place, with no re-render.
onMounted(() => feed.subscribe((ticks) => grid.value?.api?.patchRows(ticks)));
</script>
<template>
<BoGrid ref="grid" :rows="rows" :columns="columns" :height="520" :row-height="32" @cell-edit="save" class="board" />
</template> - Server rendering (Nuxt) renders the wrapper div only, and hydration matches; no
isCustomElementsetting is needed. - A bare boolean attribute is true (
<BoGrid footer row-selection />), and an event bound more than once calls every listener. - The grid sees a new
rowsarray, not changes inside one (apush, or a field set on a row). Keep the rows in ashallowRefand assign a new array, or patch through the API. classandstylego on the wrapper div, which keeps the grid’sheightuntil the element loads.
Angular and plain JavaScript
bo-grid/element registers <bo-grid> and takes the whole grid API through one config property. Arrays and functions cannot be HTML attributes, so set it from code; assigning a new config re-renders.
import { createBoGrid } from 'bo-grid/element';
const config = { columns, rows, height: 520, onCellEdit: save };
const grid = createBoGrid(config);
document.querySelector('#board')?.append(grid);
grid.config = { ...config, rows: nextRows }; // a new config re-renders In Angular, add CUSTOM_ELEMENTS_SCHEMA to the component and bind the property:
import 'bo-grid/element';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-board',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: '<bo-grid [config]="config"></bo-grid>',
})
export class BoardComponent {
config = { columns, rows, height: 520 };
} - The entry ships TypeScript types:
BoGridConfig(every<Grid>prop),BoGridElement(anHTMLElementwith a typedconfig) andcreateBoGrid(config), which sets the config before the element is attached. - Setting
configafter the element is attached is safe too: it renders an empty grid until the config arrives. - The grid’s API arrives through
onReadyin the config, once the grid is ready. - The element renders to the light DOM, so your
--bo-grid-*CSS variables and page styles apply, and it injects its own styles: there is no stylesheet to add. - It carries the Svelte runtime, so it is larger: about 87 KB gzip, against about 46 KB for the grid imported in Svelte.
- It needs the DOM: in a server-rendered app, import it on the client.
Custom cells outside Svelte
Svelte snippets cannot be passed from JavaScript, so the cell and detail snippets are Svelte-only. For rich cells, give a column a render({ value, row, column }) function that returns a DOM node or an HTML string; built-in column types and format cover most cases without one.
/** @type {import('bo-grid').ColumnDef[]} */
const columns = [
{
type: 'custom',
key: 'status',
header: 'Status',
render: ({ value }) => {
const dot = document.createElement('span');
dot.textContent = value === 'up' ? '● Live' : '○ Idle';
dot.style.color = value === 'up' ? 'var(--bo-up)' : 'var(--bo-text-dim)';
return dot;
},
},
]; Sorting, filtering, tooltips, copy and export still use the value and format; render only changes how the cell looks. See Custom cells.
Live feeds and trading helpers
bo-grid/realtime and bo-grid/trading are plain JavaScript with no framework in them, so they import the same way everywhere. A tick stream coalesces messages per key and hands the grid one batch a frame:
import { createBoGrid } from 'bo-grid/element';
import { createTickStream } from 'bo-grid/realtime';
/** @type {import('bo-grid').GridApi | undefined} */
let api;
const grid = createBoGrid({ columns, rows, height: 520, getRowId: (r) => String(r.symbol), onReady: (a) => (api = a) });
document.querySelector('#board')?.append(grid);
const stream = createTickStream({ apply: (batch) => api?.patchRows(batch) });
stream.start();
socket.onmessage = (e) => {
const q = JSON.parse(e.data);
stream.push(q.symbol, q); // cheap: coalesces per symbol, no render
}; In React the API is the component’s ref (grid.current), and in Vue the template ref’s api. The feed’s key must match the rows’ identity: set getRowId, as here, when the feed keys rows by something other than id. See Tick streams and Market conventions.
Reference
Every prop on this page is a <Grid> prop: see the Props reference.