Skip to content

StateLine

One centred line with a glyph in front of it, drawn wherever a data widget has no rows to show.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/state-line.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/state-line.json
StateLine: empty and error, in a popup list, in a table body, and under rows already drawn

React

Loading the React demo…

Svelte

Loading the Svelte demo…

A data widget shows one of three things in place of its rows.

A read that returned nothing is the empty line, muted. A read that failed is the error line, in the destructive tone. A read in flight is neither: it is skeletons shaped like the rows they stand in for, so a list holds its place when the data lands. That shape belongs to the widget that knows it, so this line covers the other two.

The label is the caller’s. Core settles the defaults, and an error falls back to what the read said and then to a fixed line, so a failure is never a blank block. The line truncates and carries the whole label as its title.

Anything passed after the line sits beside it, which is where a failed page puts its retry control.

The root carries the state as a data attribute, and the data-slot name the widget gives it, so one widget’s empty line is addressable on its own.

pad is the room the line takes.

popover is the inset of a popup list, table the inset of a table or a grid body, and none is for a line under rows already drawn, where the block around it owns the inset. The three match design rule 5, so every widget on a page leaves the same room around the same line.

A popup list carries a live region of its own, saying what the list is doing: the read in flight, the count it answered, the empty line, or what a failed read said. That is what a reader hears, and this line is what a reader sees. The two are never the same element (design rule 9), so a screen reader is told about a list that answered as well as one that did not.

proptypedefaultmeaning
state'empty' | 'error'requiredWhich state this is. An error is destructive, an empty state muted.
labelstringrequiredThe line to show. It truncates, and carries itself as a title.
iconLucideIcon | null / Component | LucideIcon | nullnullThe glyph in front of the line, drawn at size-4.
pad'popover' | 'table' | 'none''popover'The room around the line: py-6 in a popup list, py-10 in a body, and none under rows already drawn.
slotNamestring'state-line'The data-slot this block carries.

None.

None.

Not focusable. The live region beside it is what a screen reader hears.

Every widget in this registry that reads rows draws this line: the popup of a picker and of a search, the body of a table and of a grid, a tree, a grouped list, a card, and the field and column pickers. A widget passes its own label, its own glyph and the padding its surface asks for, and nothing else changes between them.