Holds a data view, such as a vehicle table or a day's chart, and shows the caller's content once its data is in, or in its place a calm message while the data loads, when there is none, or when it could not be loaded.
DataStatePanel is the wrapper that shows a data view's loading, empty and error messages in place of its content, built on EmptyState. The words "data state" elsewhere name the five vehicle dot states, which VehicleDotLegend explains, and its docs say once that its status is not a vehicle dot data state.
When to use
Use DataStatePanel to hold a data view whose data loads, such as a VehicleTable or a DayProfileChart, so that a calm message takes its place while the data loads, when there is none, or when it could not be loaded.
| Use instead | When |
|---|---|
| EmptyState | A list is empty for a reason the screen already knows, such as a search with no matches, and no data is loading |
| VehicleDotLabel | The state to show is a vehicle's, such as "No signal", not the view's request for its data |
| Alert | A problem is said beside content that stays on the screen, rather than in its place |
Props
| Name | Type | Default | Description |
|---|---|---|---|
status | 'ready' | 'loading' | 'empty' | 'error' | 'ready' | Where the data view's data stands: ready shows children; loading says "Loading" while there is no data yet; empty says the data came back with nothing in it; error says it could not be loaded. Not a vehicle dot data state |
children | ReactNode | — | The data view itself, such as a table or a chart, shown only while status is 'ready' |
description | ReactNode | — | The sentence under the message in the empty and error statuses: why, and what to try. "Try again in a minute." Not shown while loading or ready |
action | ReactNode | — | What the person can do about it in the empty and error statuses, such as a button that loads the data again, below the sentence. Not shown while loading or ready |
loadingLabel | string | The locale's | Overrides "Loading", the message while status is 'loading' |
noDataLabel | string | The locale's | Overrides "No data to show", the message while status is 'empty'. Give it what is missing when the caller knows: "No vehicles on line 5C right now" |
loadFailedLabel | string | The locale's | Overrides "Could not load the data", the message while status is 'error' |
className | string | — | Joined after pn-DataStatePanel on the root |
style | CSSProperties | — | Reaches the root |
ref | Ref<HTMLDivElement> | — | Reaches the root element |
slotProps | { message?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; emptyState?: Omit<EmptyStateProps, "action" | "children" | "title"> | undefined; } | — | Props for each part: message, emptyState. |
status'ready' | 'loading' | 'empty' | 'error', default'ready'- Where the data view's data stands:
readyshowschildren;loadingsays "Loading" while there is no data yet;emptysays the data came back with nothing in it;errorsays it could not be loaded. Not a vehicle dot data state childrenReactNode, default —- The data view itself, such as a table or a chart, shown only while
statusis'ready' descriptionReactNode, default —- The sentence under the message in the empty and error statuses: why, and what to try. "Try again in a minute." Not shown while loading or ready
actionReactNode, default —- What the person can do about it in the empty and error statuses, such as a button that loads the data again, below the sentence. Not shown while loading or ready
loadingLabelstring, default The locale's- Overrides "Loading", the message while
statusis'loading' noDataLabelstring, default The locale's- Overrides "No data to show", the message while
statusis'empty'. Give it what is missing when the caller knows: "No vehicles on line 5C right now" loadFailedLabelstring, default The locale's- Overrides "Could not load the data", the message while
statusis'error' classNamestring, default —- Joined after
pn-DataStatePanelon the root styleCSSProperties, default —- Reaches the root
refRef<HTMLDivElement>, default —- Reaches the root element
slotProps{ message?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; emptyState?: Omit<EmptyStateProps, "action" | "children" | "title"> | undefined; }, default —- Props for each part:
message,emptyState.
Use it
import { DataStatePanel } from '@mylder/perron/patterns'Ask your agent
search_docs({ query: "DataStatePanel" })Keyboard and ARIA
DataStatePanel has no keys of its own. The action in the empty and error statuses, and the caller's content once ready, are reached in reading order.
| Key | Does | From |
|---|---|---|
Tab | Moves to the first focusable element in the action, or in children once ready | The browser |
Shift+Tab | Moves back out, to whatever came before the DataStatePanel | The browser |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | None (generic div) | None | None |
message | status, a polite live region | None: a status region needs none, and its text is what it announces | Its text whenever it changes: "Loading", the empty or error title with the sentence, nothing when ready |
emptyState | EmptyState's: a paragraph title, the sentence, the caller's controls | Its title's text, from the status's owned string | None of its own |
The root takes no aria-busy while loading. Screen readers may hold back a busy element's live regions until it is no longer busy, which would silence "Loading", and nothing inside the root is half-built while it loads: the content is not rendered at all.
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the message is the visible title, sentence and action, read in place and announced through the message part's
role="status"; a caller'siconisaria-hiddeninside EmptyState and the title says what it shows. - Every landmark and named region has an accessible name: none here; the root is not a landmark, and a
statusregion takes no name. - Every default label can be overridden:
loadingLabel,noDataLabelandloadFailedLabel. - Every shape the type accepts renders, in every variant:
children,descriptionandactioneach render a string, an element and an array;descriptionandactionland in EmptyState'sdivparts, so block content is valid in them. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; a status not shown is not rendered at all, so nothing hidden stays in the tree. - Native semantics first: plain
divs and EmptyState'sptitle;role="status"only on the message part, where no element says it; the action holds the caller's ownbuttonora href. - Focus moves in reading order and returns on close: the message comes before the content in the DOM, the action after the sentence; when a change of status removes the element that had focus inside the message, focus moves to the message part (CR-6), never to the page's top.
- Every target is at least
size.target-minsquare: DataStatePanel has no target of its own; the action's targets are the caller's components, which keep their own sizes. - Loops stop when the system asks for less motion: none here; nothing moves.
Owned strings
Each status but ready shows one string of its own as the EmptyState's title. loading is in packages/perron/src/locale/en.ts already; noData and loadFailed are new keys, and agents write their Danish, Swedish and Norwegian.
| Key | English | Override | Seen or heard where |
|---|---|---|---|
loading | Loading | loadingLabel | The emptyState part's title while status is 'loading', announced by the message part |
noData | No data to show | noDataLabel | The emptyState part's title while status is 'empty', announced by the message part |
loadFailed | Could not load the data | loadFailedLabel | The emptyState part's title while status is 'error', announced by the message part |
The empty status's owned string is noData, "No data to show", with noDataLabel for the caller to name what is missing. It says only what the panel knows, that the data came back with nothing in it, without claiming why; a caller that knows says more through noDataLabel or description.
The error status's owned string is a new key, loadFailed, "Could not load the data", not "Data feed problems". "Data feed problems" is the fixed name of a kind of disruption on a line, which content design keeps for that alone; a panel whose request failed may have lost the network, not the feed, and "Could not load the data" says only what happened.
Do and don't
For a caller using the pattern, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Keep status 'ready' while fresh data replaces data already shown, and use 'loading' only while there is nothing to show yet | Flip to 'loading' on every refresh: the table blinks out every few seconds and the status region reads "Loading" each time | review |
| DD-2 | should | Name what is missing through noDataLabel when the view knows: "No vehicles on line 5C right now" | Write "No data" when the view knows what kind: content design asks to say what is happening, plainly | review |
| DD-3 | must | Give the error status one neutral action that loads the data again, such as "Try again" | Add a brand action when the panel already has one: a screen has one brand action at most | review |
| DD-4 | must | Show a vehicle that has gone silent as 'ready' content, with its vehicle dot state and the words "No signal" | Use 'error' or 'empty' for a vehicle's data state: status is about the view's data request, and the data-state words have their own components | review |
| DD-5 | should | Use plain EmptyState for a list that is empty for a reason the screen already knows, such as a search with no matches | Wrap a list in DataStatePanel only to show an empty message: it is for views whose data loads, and its status region would announce a state nothing changed | review |
| DD-6 | must | Let the message part announce the change, and put the view's own live updates, if any, in the view | Add role="status" or aria-live to children or the root: the table's every refresh would be read out, on top of the message | review |