Columns
Custom cells
Draw any cell your own way with a Svelte snippet, a component or a plain render function, and add styled tooltips to cells and headers.
When a built-in column type is not enough, draw the cell yourself. There are three ways, and each changes only how the cell looks: sorting, filtering, copying and export still use the cell’s value (and format, if set).
- A
cellsnippet on the grid, fortype: 'custom'columns, in Svelte. - A
componenton a column: a Svelte component per column, reusable across grids. - A
render(ctx)function on a column, which returns a DOM node or an HTML string. It works from React, Vue and plain JavaScript as well.
The Move column below is a cell snippet; hover the ⓘ in its header, or a cut-off company name, for a tooltip:
A cell snippet
Give the column type: 'custom' and pass a cell snippet to the grid: declared inside <Grid>, it is the cell prop, and its parameters get their types from it. It receives { row, column, value }, so one snippet can serve several custom columns by checking column.key:
<Grid {rows} {columns} height={400}>
{#snippet cell({ row })}
{@const change = Number(row.change)}
<span class="move" class:up={change >= 0}>
{change >= 0 ? '▲' : '▼'} {Math.abs(change).toFixed(2)}%
</span>
{/snippet}
</Grid> A component per column
Set component on any column. The component receives value, row, column and text (the value formatted as copy and export see it):
import StatusPill from './StatusPill.svelte';
const columns: ColumnDef[] = [
{ type: 'text', key: 'status', header: 'Status', component: StatusPill },
]; <!-- StatusPill.svelte -->
<script lang="ts">
import type { CellTypeProps } from 'bo-grid';
let { value, text }: CellTypeProps = $props();
</script>
<span class="pill" data-status={value}>{text}</span> The component updates in place as its props change. Under api.patchRows, row is a fresh view of the patched row on each patch, so read it reactively (in markup or $derived) rather than once when the component mounts.
A component is usually part of a registered cell type, so every grid that shows, say, a country uses the same cell. The Team example below does that for its Country column.
A render function
Snippets and components are Svelte features, so outside Svelte (the React and Vue components, or the <bo-grid> element) give the column a render(ctx) function. ctx is { value, row, column }. Return a DOM node, which is appended as it is, or an HTML string:
{
type: 'custom',
key: 'trend',
header: 'Trend',
render: ({ row }) => {
const el = document.createElement('span');
const up = Number(row.change) >= 0;
el.textContent = up ? '▲' : '▼';
el.style.color = up ? 'var(--bo-up)' : 'var(--bo-down)';
return el;
},
} An HTML string is inserted as
innerHTML. If it contains data you do not control, escape it first (escapeHTMLis exported) or return a node instead.
For text-only changes, a format(value, row) function is simpler than any of these, and it also changes what copy and export produce.
Tooltips and truncation
Long values end in an ellipsis. Set tooltip on a column to show the full text in a styled tooltip on hover: true shows the formatted value, and a function builds the text from the value and the row. Return '' to show no tooltip for a cell.
const columns: ColumnDef[] = [
{ type: 'text', key: 'note', header: 'Notes', width: 220, tooltip: true },
{ type: 'badge', key: 'status', header: 'Status', tooltip: (value, row) => `${value} · ${row.role}` },
{ type: 'text', key: 'bio', header: 'Bio', flex: 1, wrap: true },
]; wrap: true lets a column’s text wrap onto more lines instead of truncating; pair it with a taller row height.
Header tooltips
headerTooltip shows the same tooltip on a column’s header: what the column means, its units, its caveats. Add headerInfo: true for a small ⓘ that shows a tooltip is there:
{
type: 'progress',
key: 'workload',
header: 'Workload',
headerTooltip: 'Share of capacity allocated this sprint (0–100%).',
headerInfo: true,
} In the gallery
Rank medals and score bars drawn with custom cells:
Rich cell types, a registered country type, tooltips and wrapping:
Reference
cell Snippet<[{ row: GridRow; column: ColumnDef; value: unknown }]> Render content for type: 'custom' columns.
Example
<Grid {rows} {columns} height={480}>
{#snippet cell({ row, column })}{#if column.key === 'chart'}<MiniChart sym={row.sym} />{/if}{/snippet}
</Grid>render (ctx: CellRenderContext) => string | Node | null | undefined JS cell renderer — the framework-agnostic alternative to the cell snippet, for React/Vue/vanilla (and the <bo-grid> web component). Return an HTMLElement/Node (safe — appended as-is) or an HTML string (inserted as innerHTML — YOU must sanitize any untrusted data; prefer returning a Node for that). Overrides the cell's display only — sort, filter, tooltip, copy and export still use the value/format.
Example
{ type: 'text', key: 'sym', header: 'Symbol', render: ({ value }) => Object.assign(document.createElement('b'), { textContent: String(value) }) }component Component<CellTypeProps> Svelte component that draws this cell (receives value, row, column and the formatted text). Display only, like render. Usually supplied by a registered cellTypes entry, but any column can set it. It updates in place as its props change; under api.patchRows, row is a fresh view of the patched row on each patch (same fields, new identity), so read it reactively ($derived, markup) rather than once at mount.
Example
{ type: 'text', key: 'country', header: 'Country', component: CountryCell }format (value: unknown, row?: GridRow) => string Custom display formatter, overriding the built-in type formatter. Applies to display, tooltip, copy and (formatted) export. row is absent for aggregate cells.
Example
{ type: 'number', key: 'qty', header: 'Qty', format: (v) => Number(v).toLocaleString('vi-VN') }tooltip boolean | ((value: unknown, row: GridRow) => string) Default falseShow a styled floating tooltip on each cell (themed, instant — replaces the native title). true shows the full formatted value (handy when content truncates); a function returns custom text per value/row (any column type, including custom). Return '' to suppress for a given cell.
Example
{ type: 'text', key: 'sym', header: 'Symbol', tooltip: (_, row) => String(row.name ?? '') }wrap boolean Default falseLet long content wrap to multiple lines instead of truncating with an ellipsis. Pair with a taller rowHeight.
headerTooltip string Default undefinedShow a styled tooltip on the column header (the same themed bubble as cell tooltip) — describe what the column means, units, caveats. Hovering anywhere on the header reveals it.
headerInfo boolean Default falseRender a small ⓘ info icon in the header as a visible cue that a headerTooltip is available. Ignored without headerTooltip.