Home/Data & State/Loading & Empty States
Data & State

Loading & Empty States

What the grid shows when it has no rows to show — waiting, nothing found, load failed, every column hidden. Your data source decides which one appears, and the wording and icons are yours.

Loading keeps the layout

A data source that has not answered yet. The grid draws placeholder rows shaped like the real columns, so nothing shifts when the data lands.

#
First Name
Last Name
Department
Status

This grid's data source returns an observable that never emits, which is what a slow endpoint looks like from the inside. Note what it draws: placeholder rows shaped like the real columns, not a spinner in the middle of an empty box.

Nothing shifts when the data lands, so the reader's eye does not have to find the table twice — and a slow load reads as this table is loading rather than as something is happening somewhere. config.rowHeight sets the placeholder height too, so the skeleton matches the table it becomes.

Zero rows came back

The same config, answered with an empty array. This is what a filter that matched nothing looks like, which makes it the state your reader meets most often.

#
First Name
Last Name
Department
Status

No employees match those filters

Try adjusting your filters or search criteria.

Reached by emitting an empty array — a filter that matched nothing, a search with no hits, a brand-new account. It is the state a reader meets most often, which is why the wording is worth more thought than the others get:

messages: { empty: 'No employees match those filters' }

"No employees match those filters" tells the reader what to change. "No data" does not.

What retry actually does

A first load that threw. Press Try again — the grid re-runs your fetchRows() with the same offset, sorts and filters, and this stand-in service answers on the second attempt.

#
First Name
Last Name
Department
Status

We could not reach the employee service

This source fails on its first call and answers on the second, so the button has something to prove. Press Try again: the grid re-runs fetchRows() with the same offset, the same sorts and the same filters, and the rows arrive exactly as they would have the first time.

The grid adds no retry policy of its own and caches nothing from the failed call. Reload the page to watch it fail from the start again.

A failure after rows are loaded is smaller

The first page arrived and the next one failed. Scroll to the bottom: the rows stay where they are and the error arrives as a slim banner underneath, with its own retry.

#
First Name
Last Name
Department
Status
1
Nadia
Smith
Engineering
Inactive
2
Ben
Haddad
Marketing
Active
3
Yara
Novak
Support
Active
4
Karim
Okafor
Legal
Inactive
5
Elena
Smith
Engineering
Active
6
Walid
Haddad
Marketing
Active
7
Alice
Novak
Support
Inactive
8
Petra
Okafor
Legal
Active
9
Idris
Smith
Engineering
Active
10
Tara
Haddad
Marketing
Inactive
11
Carla
Novak
Support
Active
12
Omar
Okafor
Legal
Active
12 / 48 records

The same failure, one page later. This grid scrolls rather than pages: the first fetch succeeds and every fetch after it throws. Scroll to the bottom and watch what the grid does not do — the rows stay exactly where they were, and the error arrives as a slim banner underneath with its own retry.

Nothing configures that. The full panel replaces the grid only when there is nothing to replace, which is a first load that failed. Once rows are on screen, throwing them away would cost the reader more than the error does.

Every column hidden is its own state

Rows loaded, every column hidden. Saying nothing matched would be wrong here — the rows are there — so this state has a message of its own.

Every column is hidden

Show at least one column from the columns panel to display data.

1–10 of 48

The rows are loaded — every column is simply hidden. "No employees match those filters" would be a lie here, so the grid says something else and gives it its own key, messages.noColumns.

A reader reaches this by hiding the last visible column, which is easier to do than it sounds on a grid with a column picker.

Your data source decides which one shows

There is nothing to switch on and no flag to set. The grid reads the observable your data source returns, and its outcomes are the states:

  • Still waiting — the grid is loading from the moment it subscribes until the observable emits or throws.
  • Emitted zero rows — the empty state.
  • Errored — the error state, with a retry button.
  • Emitted rows — the table, and none of the above.

That means the states cannot drift out of sync with your data: there is no second source of truth to keep updated, and no way to be showing rows while your request is still in flight. Each example above differs only in what its fetchRows() returns — the config is the same one.

Wording and icons are separate

messages changes the text, icons changes the picture above it, and neither is required — every state has a working default. Set only what your product words or draws differently:

messages: {
  empty: 'No employees match those filters',
  error: 'We could not reach the employee service',
  retry: 'Try again',
}

One wrinkle worth knowing: the full error panel and the slim banner ship with different retry labels — Try again and Retry. Setting messages.retry sets both, which is what the examples above do.

See Custom Icons for the pictures, and Localisation for translating all of it at once — twelve languages ship with the library.

None of these states is reachable from a healthy grid, which is why this page is five grids that are each doing badly rather than one grid with a switch. In your own app they arrive unannounced — the wording is what a reader gets instead of the rows they wanted.

State options

PropertyTypeDefaultDescription
messages.emptystring'No data available'Shown when the data source returns zero rows.
messages.errorstring'Failed to load data.'Shown when the data source errors — on the full panel and on the inline banner.
messages.retrystring'Try again' / 'Retry'The retry button label. The full panel and the inline banner keep different defaults; setting this key sets both.
messages.noColumnsstring'No columns to show'Shown when every column is hidden — the rows are there, so the empty message would be wrong.
messages.allLoadedstring'All records loaded'The end-of-list note under a grid that scrolls rather than pages.
column.hiddenbooleanfalseKeeps a column out of the grid. Hide every one and the noColumns state is what is left.
config.iconsPartial<GridIconSet>shipped iconsThe pictures above the empty and error messages, and inside the retry button. See Custom Icons.
config.rowHeightnumber48Also sets the height of the loading placeholders, so the skeleton matches the table it becomes.