Empty state
What a list, a table, a panel or a page shows when it has nothing to show, and the next step.
On this page
No checklist runs yet
Code
import { Button } from "@bosunkit/components/button";
import { EmptyState } from "@bosunkit/components/empty-state";
import { ClipboardList } from "lucide-react";
import type { ReactElement } from "react";
export const description: string =
"A checklist page with no runs yet: the mark, what is empty, what to do, and the button that does it.";
export default function Showcase(): ReactElement {
return (
<EmptyState
icon={ClipboardList}
size="lg"
headingLevel={2}
title="No checklist runs yet"
description="Start one from a template, such as Leaving harbour or Night passage."
actions={
<>
<Button variant="primary">Start a run</Button>
<Button>See the templates</Button>
</>
}
/>
);
}
An empty state is a mark, one line saying what is empty, one saying why or what happens next, and the next step as a
button or a link. Its kind says why there is nothing: empty (nothing recorded), no-results (a search or a
filter matched nothing), unavailable (the data cannot be shown here, such as a tide where no tables cover the
water) and done (nothing is left to do). Each kind draws its own mark; icon takes another, or null for none.
It is not a message: an empty log is not an error, so it wears no message color, and a done state takes the success
green on its mark only. A lost connection to a system bosund reads is an alert, never an empty state. A no-results
state reads its title out politely when it appears in the place of results while the crew type; announce turns that
on or off for any kind. headingLevel makes the title a heading for a state that stands for a section or a page, and
fill centers it in the height of its container. On a phone its actions stack to the screen's width.
Examples
A search that matched nothing
No crew member matches “Ana”
Code
import { Button } from "@bosunkit/components/button";
import { EmptyState } from "@bosunkit/components/empty-state";
import type { ReactElement } from "react";
export const description: string =
"A crew search that matched nobody: the state is read out as it replaces the results, and offers to clear the search.";
export default function NoResults(): ReactElement {
return (
<EmptyState
kind="no-results"
title="No crew member matches “Ana”"
description="Check the spelling, or search by nickname or role."
actions={<Button>Clear the search</Button>}
/>
);
}
Data that cannot be shown here
No tide prediction for this position
Code
import { EmptyState } from "@bosunkit/components/empty-state";
import type { ReactElement } from "react";
export const description: string =
"A tide panel where no tide tables cover the water: it says so, and what the depth shown is based on.";
export default function Unavailable(): ReactElement {
return (
<EmptyState
kind="unavailable"
title="No tide prediction for this position"
description="The tide tables don't cover these waters, so the depth shown is the charted depth only."
/>
);
}
Nothing left to do
Every task is signed off
Code
import { EmptyState } from "@bosunkit/components/empty-state";
import type { ReactElement } from "react";
export const description: string = "The maintenance list with every task signed off: nothing left to do, in the success green.";
export default function Done(): ReactElement {
return (
<EmptyState
kind="done"
title="Every task is signed off"
description="The next service, the engine's 250-hour, falls due in 38 engine hours."
/>
);
}
In a table
No voyages yet
Code
import { DataTable, type Column } from "@bosunkit/components/data-table";
import { EmptyState } from "@bosunkit/components/empty-state";
import type { ReactElement } from "react";
export const description: string = "A voyage table with no rows: the small state takes the table's empty content.";
const COLUMNS: Column[] = [
{ key: "name", label: "Voyage" },
{ key: "from", label: "From" },
{ key: "to", label: "To" },
];
export default function InATable(): ReactElement {
return (
<DataTable
columns={COLUMNS}
rows={[]}
empty={<EmptyState size="sm" title="No voyages yet" description="A voyage starts when the vessel leaves its berth." />}
/>
);
}
Sizes
No attachments
No guests aboard
No passages planned
Code
import { EmptyState } from "@bosunkit/components/empty-state";
import type { ReactElement } from "react";
export const description: string = "The three sizes: a list's cell, a panel, and a page with nothing else in it.";
export default function Sizes(): ReactElement {
return (
<div style={{ display: "grid", gap: "var(--space-md)" }}>
<EmptyState size="sm" align="start" title="No attachments" />
<EmptyState size="md" title="No guests aboard" description="A guest is added from the crew page." />
<EmptyState size="lg" title="No passages planned" description="Plan one from the chart." />
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| titlerequired | string | – | What is empty, in one line, translated by the caller ("No checklist runs yet"). |
| description | ReactNode | – | Why it is empty or what happens next, translated by the caller ("The morning briefing is at 07:00 ship's time."). |
| kind | EmptyStateKind | undefined | "empty" | Why there is nothing to show; empty unless set. It chooses the mark and whether the state is announced. |
| icon | LucideIcon | ReactElement | null | undefined | – | Another mark: a Lucide icon, or an element such as an OpenBridge icon; null draws none. The kind's unless set. |
| actions | ReactNode | – | The next step: one or two buttons or links, the first of them the main one ("Start a run"). |
| headingLevel | NestedHeadingLevel | undefined | – | Makes the title a heading at this level, for a state that stands for a section or a page; plain text unless set. |
| size | EmptyStateSize | undefined | "md" | How much room it takes; md unless set. |
| align | "center" | "start" | undefined | "center" | center for a panel or a page, start beside other content in a column; center unless set. |
| fill | boolean | undefined | false | Grows to the height of its container and centers itself in it, for a page or a panel with nothing else in it. |
| announce | boolean | undefined | – | Reads the title out politely when the state appears (role="status"), for one that replaces results as the crew
type or filter. On for no-results, off for the others, unless set. |
| children | ReactNode | – | More content under the description, such as a short list of what to try. |
| className | string | undefined | – | Classes added to the state's own. |
| testId | string | undefined | – | Becomes data-testid on the root. |
| motion | MotionProp | undefined | – | How the state moves. By default it fades in and out inside a MotionPresence, as results give way to it, and its
new words fade in when the title changes. A preset or { enter, exit, change } chooses; false turns it off. |
Related
- Section message: a message to one panel, such as a verdict on its data.
- Data table: takes an empty state as its
emptycontent. - Skeleton: what a panel shows while its data loads, before it knows it is empty.