Troubleshooting

Symptom first. The error reference covers the messages the library throws; this page covers the quiet failures - the grid mounts but shows nothing, a feature you switched on does nothing, the page scrolls instead of the grid. Each entry names the cause the way you would see it and the smallest change that fixes it.

The grid renders but shows no rows

Work down this list; the first four cover almost every report.

containerHeight="100%" in a parent with no height

The grid draws its rows inside a scroll shell whose height comes from the containerHeight prop. The default is 520 px. "100%" hands the decision to the parent, and a parent that has no height of its own (a plain <div>, or a flex child without min-height: 0) resolves to 0 px: the header still shows, the rows exist in the DOM, and the shell that would display them is zero pixels tall.

Give the parent a height, or use a fixed number:

<!-- either -->
<SvGrid {data} {columns} containerHeight={480} />

<!-- or a parent that has a height to give -->
<div class="flex flex-col h-screen">
  <header>…</header>
  <div class="flex-1 min-h-0">
    <SvGrid {data} {columns} containerHeight="100%" />
  </div>
</div>

The min-h-0 matters: flex children default to min-height: auto, which keeps the inner scroll container from shrinking and turns "the grid scrolls" into "the page scrolls". Sizing the grid has the full recipe.

A grid inside a hidden tab (display: none) is the same situation with a happy ending: it stays unmeasured until the tab opens, then a resize observer measures the box and the rows appear on their own.

The rows arrived, but the grid was not told

The grid re-reads data when the prop's reference changes, and it tracks a Svelte 5 $state array deeply, so both of these work:

<script lang="ts">
  let rows = $state<Person[]>([])
  $effect(() => { fetchPeople().then((next) => (rows = next)) })   // replace
  function add(p: Person) { rows.push(p) }                          // mutate in place
</script>

What does not work is mutating a plain array: const rows: Person[] = [] followed by rows.push(...) after a fetch changes nothing the grid can see. Declare the array with $state, or assign a new array.

data was undefined for a render

data has to be an array on every render, including the first. Mounting with data={rows} while rows is still undefined throws at mount (Cannot read properties of undefined (reading 'length')). Start from [] and say that a load is in flight with loading:

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

  type Person = { id: number; name: string; city: string }
  const columns: GridColumns<Person> = [
    { field: 'name', header: 'Name', width: 180 },
    { field: 'city', header: 'City', width: 150 },
  ]

  let rows = $state<Person[]>([])
  let loading = $state(false)

  async function load() {
    loading = true
    await new Promise((r) => setTimeout(r, 600))
    rows = [
      { id: 1, name: 'Ada Lovelace', city: 'London' },
      { id: 2, name: 'Grace Hopper', city: 'New York' },
    ]
    loading = false
  }
</script>

<button type="button" onclick={load}>Load</button>
<button type="button" onclick={() => (rows = [])}>Clear</button>

<SvGrid data={rows} {columns} {loading} containerHeight={200} emptyMessage="Nothing loaded yet - press Load." />

Headers show, every cell is blank

The rows are there; the columns cannot read them. A ColumnDef reads its value through field, and field: 'firstName' on rows that carry first_name reads undefined in every cell. Check the field names against one row of real data (console.log(rows[0])), or give the column a fieldFn / cell renderer that computes the value. A column with none of the three throws Column "<id>" has no field, fieldFn, or cell renderer, so a silent blank column is always a field-name mismatch.

loading or error was left set

loading replaces the rows with the loading state until it goes back to false; error replaces them with the message and wins over both loading and the empty state. A fetch that throws before it resets the flag leaves the grid on the placeholder for good - reset in a finally:

<script lang="ts">
  let loading = $state(false)
  let error = $state<string | null>(null)

  async function refresh() {
    loading = true
    error = null
    try {
      rows = await fetchPeople()
    } catch (e) {
      error = e instanceof Error ? e.message : 'Could not load people.'
    } finally {
      loading = false
    }
  }
</script>

<SvGrid data={rows} {columns} {loading} {error} />

data={[]} with neither flag set shows emptyMessage (the localized "no rows" text by default), which is the state you want after a filter that matched nothing - see the empty state. Demo 79 wires all four states - loading skeleton, typed error with retry, empty, ready - around one fetch:

Open the live example: Loading from REST (Server-Side Data)

Sorting, filtering or editing does nothing

Every capability is off until you turn it on. A bare <SvGrid> is a read-only table with keyboard navigation and selection; clicking a header sorts nothing, and there is no filter menu and no editor. Add the boolean props:

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

  type Person = { id: number; name: string; city: string; age: number }
  const rows: Person[] = [
    { id: 1, name: 'Ada Lovelace', city: 'London', age: 36 },
    { id: 2, name: 'Grace Hopper', city: 'New York', age: 45 },
    { id: 3, name: 'Linus Torvalds', city: 'Portland', age: 54 },
  ]
  const columns: GridColumns<Person> = [
    { field: 'name', header: 'Name', width: 180, editorType: 'text' },
    { field: 'city', header: 'City', width: 150, editorType: 'text' },
    { field: 'age', header: 'Age', width: 90, editorType: 'number' },
  ]
</script>

<SvGrid data={rows} {columns} sortable filterable editable containerHeight={200} />

sortable registers rowSortingFeature, filterable registers columnFilteringFeature (the menu is filterMode="menu", the default), editable is enableInlineEditing. Editing also wants an editorType on the columns you intend to edit; a column without one falls back to the text editor. Features lists every shortcut and the features={tableFeatures({ ... })} form behind it.

The page scrolls instead of the grid

The grid is taller than its parent and the parent lets it grow. Either the parent is a flex child without min-height: 0 (see the first entry) or containerHeight="auto" is set, which is exactly the "grow to the content" mode. Use a fixed containerHeight, or "100%" under a parent with a real height.

Enterprise features show a watermark

@svgrid/enterprise runs every feature without a key and marks the grid with a watermark plus one console nudge per page load. Set the key once at startup with setLicenseKey(...) from @svgrid/enterprise (the @svgrid/enterprise/license subpath exports the same function) - Enterprise licensing lists what changes with and without it.

The build fails inside @svgrid/grid on Svelte 4

@svgrid/grid is written with Svelte 5 runes and declares svelte ^5 as a peer dependency. On a Svelte 4 project the compiler stops inside the package's own .svelte files. There is no Svelte 4 build; the fix is the Svelte 5 upgrade. The comparison page lists what the Svelte 4 ecosystem offers if the upgrade is not on the table yet.

Selection or edits jump to the wrong row

Selection, expansion and edit state are keyed by row id, and without getRowId the id is the array index - which changes under a sort, a filter, or an insert at the top. Pass a getRowId that returns a value unique to each row (a primary key, a UUID). Two rows returning the same id share one state, so a duplicate key shows up as two rows highlighting together. Row identity has the details.

Frequently asked questions

Why is my SvGrid empty?

In order of likelihood: containerHeight="100%" under a parent with no height, so the scroll shell is 0 px tall; rows pushed into a plain array the grid cannot observe (use $state or assign a new array); data that was undefined for the first render (start from []); or column field names that do not exist on the rows, which leaves the headers up and every cell blank. The entries above give the fix for each.

Why does clicking a column header not sort?

Sorting is off by default. Add sortable to <SvGrid> (or register rowSortingFeature through features). The same applies to filtering (filterable) and editing (editable).

Why does the whole page scroll instead of the grid?

The grid's parent lets it grow: a flex child needs min-height: 0 for the inner scroll container to shrink, and containerHeight="auto" deliberately grows to the content. Use a fixed containerHeight or "100%" inside a parent that has a height.

See also