Full-width rows

"Full-width rows" are rows whose content takes the entire grid width instead of being cell-by-cell - useful for inline editors, banner ads, section dividers, and detail-row expansions.

Open the live example: Tree + master/detail (Tree & Hierarchy)

Detail-row API (shipped)

Full-width rows are built in via isDetailRow + renderDetailRow: mark a row as a detail row and it renders as a real full-width colspan cell hosting your snippet - a nested grid, a form, timelines, anything.

<SvGrid
  {data} {columns}
  isDetailRow={(row) => row.kind === 'detail'}
  renderDetailRow={DetailPanel}
/>

See Master / detail (nested grids) for the full expand/collapse pattern, and demo 106-detail-rows for a multi-panel detail.

A single sticky footer row is also available via summary (off by default):

<SvGrid {data} {columns} summary />

It sums a numeric column and counts the rest. Pick a different aggregate per column with that column's own summary option ('avg', 'min', 'max', 'count', a custom function), or summary: false to leave the cell blank.

A detail row under its parent

Mark the row, render the panel. isDetailRow answers "is this one of them", renderDetailRow is a snippet that gets the row and its index, and the grid gives it a real colspan cell across every column.

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

  // One flat type, not a union: `field` is `keyof TData`, and `keyof` over a
  // union keeps only the shared keys, so `field: 'customer'` would not type.
  type Row = {
    kind: 'order' | 'detail'
    id: string
    customer?: string
    total?: number
    lines?: string[]
  }

  const rows: Row[] = [
    { kind: 'order',  id: 'A-1',  customer: 'Ada Lovelace', total: 240 },
    { kind: 'detail', id: 'A-1d', lines: ['2 x Cycling cap', '1 x Road bottle'] },
    { kind: 'order',  id: 'A-2',  customer: 'Grace Hopper', total: 96 },
    { kind: 'detail', id: 'A-2d', lines: ['3 x Patch kit'] },
  ]

  const columns: GridColumns<Row> = [
    { field: 'id',       header: 'Order',    width: 110 },
    { field: 'customer', header: 'Customer', width: 190 },
    { field: 'total',    header: 'Total',    width: 120,
      format: { type: 'currency', currency: 'USD' } },
  ]
</script>

{#snippet DetailPanel(props: { row: Row; rowIndex: number })}
  <div style="padding: 8px 14px; font-size: 12px; opacity: 0.85;">
    {#each props.row.lines ?? [] as line}
      <div>{line}</div>
    {/each}
  </div>
{/snippet}

<SvGrid
  data={rows}
  {columns}
  rowHeight={40}
  isDetailRow={(row) => row.kind === 'detail'}
  renderDetailRow={DetailPanel}
/>

Section dividers are the same mechanism

Nothing says a full-width row has to be a detail panel. Interleave marker rows into the data and the same two props give you group headers, banners, or an end-of-list note - no extra API, and sorting still applies to the real rows if you turn it on for the columns that carry values.

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

  type Row = {
    kind: 'person' | 'divider'
    id: number
    name?: string
    city?: string
    label?: string
  }

  const rows: Row[] = [
    { kind: 'divider', id: 100, label: 'Engineering' },
    { kind: 'person',  id: 1, name: 'Ada Lovelace', city: 'London' },
    { kind: 'person',  id: 2, name: 'Grace Hopper', city: 'New York' },
    { kind: 'divider', id: 101, label: 'Platform' },
    { kind: 'person',  id: 3, name: 'Linus Torvalds', city: 'Portland' },
  ]

  const columns: GridColumns<Row> = [
    { field: 'name', header: 'Name', width: 200 },
    { field: 'city', header: 'City', width: 170 },
  ]
</script>

{#snippet Divider(props: { row: Row; rowIndex: number })}
  <div style="padding: 6px 14px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; opacity: 0.65;">
    {props.row.label}
  </div>
{/snippet}

<SvGrid
  data={rows}
  {columns}
  rowHeight={32}
  isDetailRow={(row) => row.kind === 'divider'}
  renderDetailRow={Divider}
/>

See also