Cell components
For any cell whose content is more than a string, use cell: with
renderSnippet or renderComponent.
Open the live example: Custom cells + themes (Rows & Cells)
Snippet
The examples on this page run against these rows:
<script lang="ts">
import { SvGrid } from '@svgrid/grid'
type Person = {
id: number
name: string
email: string
department: string
age: number
salary: number
city: string
startDate: string
active: boolean
}
</script>
<script lang="ts">
import { renderSnippet, type GridColumns } from '@svgrid/grid'
const columns: GridColumns<Person> = [
{
field: 'status',
header: 'Status',
cell: (ctx) => renderSnippet(Pill, { value: String(ctx.getValue()) }),
},
]
</script>
{#snippet Pill(p: { value: string })}
<span class="pill pill-{p.value}">{p.value}</span>
{/snippet}
Snippets are the right choice when the renderer is local to the page and small.
Component
import StatusBadge from './StatusBadge.svelte'
import { renderComponent } from '@svgrid/grid'
{
field: 'status',
cell: (ctx) => renderComponent(StatusBadge, { status: ctx.getValue() }),
}
Components are the right choice when the renderer is reused across multiple grids, has its own state, or needs lifecycle hooks.
CellContext
The argument the grid passes to your cell callback:
type CellContext<TData> = {
cell: Cell<TData>
row: Row<TData>
column: Column<TData>
table: SvGrid<TData>
getValue: () => unknown
}
getValue()- the accessed value (post-field/fieldFn).row.original- the rawTDataobject.row.getAllCells()- every cell in the row, for sibling reads.column.columnDef- the originalColumnDef.table- the headless grid instance, with state and actions.
Inline string
If your cell content is a plain string and you just want to format it, use
format or formatter - not cell. See
Text formatting.
Performance
Cell renderers run once per visible cell on each grid update. For large virtualized grids, keep them cheap:
- avoid
JSON.stringify - avoid
new Date()per cell - pre-compute formatters at module scope - avoid creating new objects inside the snippet template
Common patterns
- Avatar + name - return a snippet that pulls first/last name from
ctx.row.original. See demo 10. - Status pill - class-derived background. See demo 10.
- Inline progress bar -
<div role="progressbar" aria-valuenow>. See demo 10. - Hyperlink -
<a href="/people/{ctx.row.original.id}">{ctx.getValue()}</a>.
A cell that reads the whole row
renderSnippet passes whatever you give it, so the cell can use fields the
column does not name. That is the difference between rendering a value and
rendering a record.
<script lang="ts">
import { SvGrid, renderSnippet, type GridColumns, type SvGridApi } from '@svgrid/grid'
type Person = {
id: number
name: string
department: string
city: string
age: number
salary: number
active: boolean
}
const people: Person[] = [
{ id: 1, name: 'Ada Lovelace', department: 'Engineering', city: 'London', age: 36, salary: 142000, active: true },
{ id: 2, name: 'Grace Hopper', department: 'Engineering', city: 'New York', age: 45, salary: 168000, active: true },
{ id: 3, name: 'Linus Torvalds', department: 'Platform', city: 'Portland', age: 54, salary: 155000, active: false },
{ id: 4, name: 'Radia Perlman', department: 'Networking', city: 'Seattle', age: 49, salary: 161000, active: true },
]
const columns: GridColumns<Person> = [
{ field: 'name', header: 'Person', width: 260,
cell: (ctx) => renderSnippet(PersonCell, { row: ctx.row.original }) },
{ field: 'salary', header: 'Salary', width: 140,
format: { type: 'currency', currency: 'USD' } },
]
</script>
{#snippet PersonCell(props: { row: Person })}
<span style="display: inline-flex; align-items: center; gap: 8px;">
<span style="width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; background: color-mix(in srgb, currentColor 12%, transparent); font-size: 11px;">
{props.row.name.split(' ').map((w) => w[0]).join('')}
</span>
<span>
<span style="display: block;">{props.row.name}</span>
<span style="display: block; font-size: 11px; opacity: 0.6;">{props.row.department}</span>
</span>
</span>
{/snippet}
<SvGrid data={people} {columns} rowHeight={44} />
Falling back when there is nothing to show
A renderer runs for every row including the empty ones, so decide what an absent value looks like. A dash reads as "none"; a blank cell reads as a bug.
<script lang="ts">
import { SvGrid, renderSnippet, type GridColumns, type SvGridApi } from '@svgrid/grid'
type Person = {
id: number
name: string
department: string
city: string
age: number
salary: number
active: boolean
}
const people: Person[] = [
{ id: 1, name: 'Ada Lovelace', department: 'Engineering', city: 'London', age: 36, salary: 142000, active: true },
{ id: 2, name: 'Grace Hopper', department: 'Engineering', city: 'New York', age: 45, salary: 168000, active: true },
{ id: 3, name: 'Linus Torvalds', department: 'Platform', city: 'Portland', age: 54, salary: 155000, active: false },
{ id: 4, name: 'Radia Perlman', department: 'Networking', city: 'Seattle', age: 49, salary: 161000, active: true },
]
const withGaps = people.map((p, i) => ({ ...p, city: i % 2 ? '' : p.city }))
const columns: GridColumns<Person> = [
{ field: 'name', header: 'Name', width: 190 },
{ field: 'city', header: 'City', width: 160,
cell: (ctx) => renderSnippet(CityCell, { value: String(ctx.getValue() ?? '') }) },
]
</script>
{#snippet CityCell(props: { value: string })}
{#if props.value}
<span>{props.value}</span>
{:else}
<span style="opacity: 0.4;">-</span>
{/if}
{/snippet}
<SvGrid data={withGaps} {columns} />
See also
Related articles
- Progress and Percentage Bar Cells in SvGrid - Build in-cell progress bars in your Svelte 5 data grid - with color thresholds, accessible markup, and sorting that still works.
- A Fill Handle (Drag to Fill) in SvGrid - Build a working spreadsheet-style fill handle on top of SvGrid's cell selection and editing - pointer tracking, range highlighting, series fill, and undo/redo integration all covered.
- An Editable Select / Dropdown Cell in SvGrid - Build a dropdown cell editor in SvGrid using a Svelte 5 snippet - constrain user input to a fixed option set while keeping sort, filter, and undo working on the underlying value.