bo-grid v3.1.0

Columns

.md

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 cell snippet on the grid, for type: 'custom' columns, in Svelte.
  • A component on 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:

custom-cells.svelte GitHub ↗

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 (escapeHTML is 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,
}

Rank medals and score bars drawn with custom cells:

Leaderboard.svelte Open GitHub ↗

Rich cell types, a registered country type, tooltips and wrapping:

Team.svelte Open GitHub ↗

Reference

Grid props from the types
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>
Column options from the types
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 false

Show 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 false

Let long content wrap to multiple lines instead of truncating with an ellipsis. Pair with a taller rowHeight.

headerTooltip string Default undefined

Show 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 false

Render a small ⓘ info icon in the header as a visible cue that a headerTooltip is available. Ignored without headerTooltip.