<!-- https://svgrid.com/docs/recipes/cell-renderer-patterns/ - SvGrid documentation as markdown. Index of every page: https://svgrid.com/llms.txt -->

# Cell renderer patterns

Every supported shape for the `cell:` slot on a `ColumnDef`, with the
trade-offs for each.

## The four shapes

`cell:` accepts one of:

| Shape                              | When to use                                                    |
| ---------------------------------- | -------------------------------------------------------------- |
| `string`                           | A literal placeholder ("TBD"). Rare.                           |
| `(ctx) => string \| number`        | Computed text, no DOM control.                                 |
| `(ctx) => renderSnippet(...)`      | Inline Svelte 5 snippet defined in the same file.              |
| `(ctx) => renderComponent(...)`    | Mount a real Svelte component imported from another file.      |

`ctx` is `{ row: Row<TData>, column, table, getValue }` - the same
context TanStack-style tables emit. Everywhere below `ctx.row.original`
is your `TData` row.

## 1. Literal string

The examples on this page run against these rows:

```svelte
<script lang="ts">
  import { SvGrid, tableFeatures, rowSortingFeature, columnFilteringFeature } from '@svgrid/grid'

  const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
</script>
```

```ts
{ field: 'amount', header: 'Amount', cell: 'TBD' }
```

Used when bootstrapping. The cell renders the literal across every row.

## 2. Computed function

```ts
{
  field: 'amount', header: 'Amount', align: 'right',
  cell: (ctx) => {
    const n = ctx.row.original.amount
    if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(1)}M`
    if (n >= 1_000)     return `$${Math.round(n / 1_000)}k`
    return `$${n}`
  },
}
```

Cheapest custom renderer. No DOM control - the grid wraps the return
value in its own `<td>`. Use for formatted text, status acronyms,
computed totals.

For built-in formats (currency, percent, date, units) prefer the
declarative [`format:`](https://github.com/sv-grid/sv-grid/blob/main/docs/reference/ColumnDef.md#format) field - it
runs through `Intl.NumberFormat` cached per locale.

## 3. Inline snippet

```svelte
<script lang="ts">
  import { renderSnippet, type ColumnDef } from '@svgrid/grid'

  type Sale = { amount: number; up: boolean }
  const columns: ColumnDef<typeof features, Sale>[] = [
    {
      field: 'amount', header: 'Amount', align: 'right',
      cell: (ctx) => renderSnippet(Pill, { sale: ctx.row.original }),
    },
  ]
</script>

{#snippet Pill(props: { sale: Sale })}
  <span class="pill" class:up={props.sale.up} class:down={!props.sale.up}>
    {props.sale.up ? '▲' : '▼'} ${props.sale.amount.toLocaleString()}
  </span>
{/snippet}

<style>
  :global(.pill.up)   { background: rgba(34,197,94,0.18);  color: #15803d; }
  :global(.pill.down) { background: rgba(239,68,68,0.18);  color: #b91c1c; }
</style>
```

The snippet is local to the component, with access to its scope. Best
for one-off renderers that need DOM control but don't merit their own
file. The styles need `:global` because the snippet renders into the
grid's body, not the consumer's component subtree.

## 4. Imported component

```svelte
<script lang="ts">
  import { renderComponent } from '@svgrid/grid'
  import BadgeCell from './BadgeCell.svelte'

  const columns = [
    {
      field: 'amount', header: 'Amount',
      cell: (ctx) => renderComponent(BadgeCell, {
        label: `$${ctx.row.original.amount.toLocaleString()}`,
        tone:  ctx.row.original.up ? 'good' : 'bad',
      }),
    },
  ]
</script>
```

```svelte
<!-- BadgeCell.svelte -->
<script lang="ts">
  let { label, tone }: { label: string; tone: 'good' | 'bad' } = $props()
</script>
<span class="bc" class:good={tone === 'good'} class:bad={tone === 'bad'}>{label}</span>
```

Use when the renderer is reused across columns or grids, or when it
carries its own non-trivial state.

## Headers, footers, editors - same shapes

The `header:` and `footer:` fields on `ColumnDef` accept the same four
shapes - any function-valued header is rendered via the same path. So
you can plug your own `<HeaderToolbar />` component without forking
`<SvGrid>`.

Custom inline editors use `cellEditor:` and follow the
`(ctx) => renderSnippet(...)` pattern - see the [`84-editor-types`
demo](https://svgrid.com/demos/84-editor-types/).

## Try it

`renderSnippet(snippet, params)` is the whole custom-cell API. The snippet gets
whatever you pass it, so a cell can read the row rather than just the value -
which is what a status pill or a coloured figure needs.

```svelte
<script lang="ts">
  import { SvGrid, renderSnippet, type GridColumns } from '@svgrid/grid'

  type Person = {
    id: number
    name: string
    department: string
    city: string
    age: number
    salary: number
  }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace',   department: 'Engineering', city: 'London',   age: 36, salary: 142000 },
    { id: 2, name: 'Grace Hopper',   department: 'Engineering', city: 'New York', age: 45, salary: 168000 },
    { id: 3, name: 'Linus Torvalds', department: 'Platform',    city: 'Portland', age: 54, salary: 155000 },
    { id: 4, name: 'Radia Perlman',  department: 'Networking',  city: 'Seattle',  age: 49, salary: 161000 },
    { id: 5, name: 'Barbara Liskov', department: 'Platform',    city: 'Boston',   age: 52, salary: 172000 },
  ]

  const columns: GridColumns<Person> = [
    { field: 'name',       header: 'Name',       width: 190 },
    { field: 'department', header: 'Department', width: 150 },
    { field: 'salary',     header: 'Salary',     width: 130, format: { type: 'currency', currency: 'USD' } },
  ]
</script>

{#snippet Money(props: { row: Person })}
  <span style="font-variant-numeric: tabular-nums; color: {props.row.salary > 160000 ? '#b45309' : 'inherit'}">
    {props.row.salary.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
  </span>
{/snippet}

{#snippet Dept(props: { row: Person })}
  <span style="padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, currentColor 10%, transparent); font-size: 12px;">
    {props.row.department}
  </span>
{/snippet}

<SvGrid
  data={people}
  columns={[
    { field: 'name', header: 'Name', width: 190 },
    { field: 'department', header: 'Department', width: 160,
      cell: (ctx) => renderSnippet(Dept, { row: ctx.row.original }) },
    { field: 'salary', header: 'Salary', width: 140,
      cell: (ctx) => renderSnippet(Money, { row: ctx.row.original }) },
  ] satisfies GridColumns<Person>}
/>
```

## See also

- [Sparkline-cells recipe](https://svgrid.com/docs/recipes/sparkline-cells/) - a non-trivial component cell
- [Heatmap-cells recipe](https://svgrid.com/docs/recipes/heatmap-cells/) - computed-function cell with conditional formatting
- [Barcode-cells recipe](https://svgrid.com/docs/recipes/barcode-cells/) - real SVG rendered per row
- [Conditional row coloring](https://svgrid.com/docs/recipes/conditional-row-coloring/) - styling without a custom renderer
- [`<SvGrid>` reference](https://github.com/sv-grid/sv-grid/blob/main/docs/reference/SvGrid.md) - `renderSnippet` / `renderComponent` helpers

---

SvGrid is the Svelte 5 data grid (`npm install @svgrid/grid`, MIT). This page: https://svgrid.com/docs/recipes/cell-renderer-patterns/ . All docs: https://svgrid.com/llms.txt
