People need to know why a panel is empty and what to do next.
Updated September 9, 2026
Problem
An empty panel can mean there are no matching records, setup is incomplete, or data could not be loaded. People need to know which one applies.
Solution
Explain why the panel is empty and offer the relevant next step. A filtered result can suggest clearing filters; an unfinished setup can link to the missing step.
Keep an empty result distinct from loading, failure, and a measured zero. Only display zero when the data supports it.
Use specific labels and actions that match the current interface. Avoid treating permission or connection failures as an ordinary empty result.
Use when
A panel or list can have no content, including during first use or after filtering.
Don’t use when
Never skip the state. Do skip the illustration. A dashboard someone opens forty times a day shouldn’t spend viewings five through forty on a friendly graphic explaining a condition they already understand.
Trade-offs
Generic messages are easy to reuse but often hide the cause. Empty-state instructions become misleading when controls change. Each cause needs a useful response without overwhelming the panel with explanation.
Checklist
Which empty is this: never configured, configured but no data yet, filtered down to nothing, or failed?
Does the copy distinguish “no data” from “zero”, and would the viewer read it correctly at a glance?
If a filter caused it, does the state name the filter and offer to clear it?
If a time range caused it, does it say the range? “No records for the selected date range” answers a question a blank panel leaves open.
Is there exactly one obvious next action, and does this viewer have permission to take it?
What does this look like on the fortieth viewing rather than the first?
Can an error ever land here instead of in an error state?
Does the empty state occupy the space the content will, so the layout doesn’t jump when data arrives?
Is it real text a screen reader can reach, or a background image?
Who owns this copy when the thing it references gets renamed?
Compare
Grafana is two products on this question. Its alerting side treats No Data as a state you have to consciously map, with four options and a default that won’t call missing data healthy. Its panel side renders a missing value as a hyphen, which on a wallboard ten feet away is a dash that tells the viewer nothing. Sentry makes the empty project be the install flow, putting the DSN and the snippet on the page, on the reasonable bet that a project with no events is a project nobody has wired up yet. Honeycomb is query-first, so an empty result leaves the query builder in place and the state implicitly reads “your query was fine and matched nothing”, which is a different and more useful claim than “there is no data here”. Netdata mostly sidesteps the first-run version by auto-discovering what’s running on the node, which moves the empty state up a level: the question becomes whether the agent is connected, not whether a chart has data.
Related
Loading state shows a request still in progress. Error and stale state handles failures and outdated data. Freshness indicator adds the last update time.
Filter bar may explain why no records match. Data source badge identifies the queried source.
Wireframe — the pattern's anatomy, not any one product's version of it
Implementation
Nothing to show, and the reason decides the copy. No data yet, no data matching the filter, and query failed are three different situations, and a single friendly illustration for all three is how readers learn to distrust the page.
The query returned nothing for region = eu-west, client = ios.
Checkout errors
0
in the last hour, from 2.1M requests
EmptyPanel.tsxA closed union of the three kinds. The filtered kind names its filters, the zero carries its denominator.
import { Button } from "@/components/ui/button";import { Card } from "@/components/ui/card";/** * Three kinds of nothing, as a closed union. A careless product renders them * identically, and they mean opposite things: one says the system is healthy, * one says you can't tell, one says nobody has wired this up yet. Each kind * carries the one thing its copy needs, so the panel cannot say "No data" * without saying which. */export type Empty = /** Never received a sample. The next action is setup, not a filter. */ | { kind: "never"; onConnect: () => void } /** Configured, but the query matched nothing. At least one filter, because the copy names them. */ | { kind: "filtered"; filters: [string, ...string[]]; onClear: () => void } /** A real zero. It needs its denominator: zero out of two million is good news, zero out of zero is the first kind. */ | { kind: "zero"; window: string; outOf: string };const WORDS = ["no", "one", "two", "three", "four", "five"];const count = (n: number, noun: string) => `${WORDS[n] ?? n} ${noun}${n === 1 ? "" : "s"}`;export function EmptyPanel({ title, state }: { title: string; state: Empty }) { return ( <Card className={`h-[150px] p-4 ${state.kind === "filtered" ? "border-2 border-chart-1" : ""}`}> <p className="text-[11px] uppercase tracking-wide text-muted-foreground">{title}</p> {state.kind === "never" && ( <> <p className="mt-5 text-sm text-card-foreground">Nothing here yet</p> <p className="mt-1 text-[11px] text-muted-foreground">This panel has never received a sample.</p> <Button variant="outline" size="sm" className="mt-3 h-6 px-2 text-[10px]" onClick={state.onConnect}>connect a source</Button> </> )} {state.kind === "filtered" && ( <> <p className="mt-5 text-sm text-card-foreground">No data</p> <p className="mt-1 text-[11px] text-muted-foreground"> The query returned nothing for {state.filters.join(", ")}. </p> <Button variant="outline" size="sm" className="mt-3 h-6 px-2 text-[10px]" onClick={state.onClear}> clear {count(state.filters.length, "filter")} </Button> </> )} {state.kind === "zero" && ( <> <p className="mt-4 text-4xl font-medium tabular-nums text-card-foreground">0</p> <p className="mt-2 text-[11px] tabular-nums text-muted-foreground">in the {state.window}, from {state.outOf}</p> </> )} </Card> );}
demo.tsxHow it is called: the same panel in all three kinds. Clearing the filters turns the middle one into the real zero.
import { useState } from "react";import { EmptyPanel, type Empty } from "./EmptyPanel";/** * The same panel three times, once per kind of empty. Clearing the two * filters on the middle one turns it into the real zero on the right, which * is the whole distinction: nothing matched, against nothing happened. */export default function Demo() { const [middle, setMiddle] = useState<Empty>({ kind: "filtered", filters: ["region = eu-west", "client = ios"], onClear: () => setMiddle({ kind: "zero", window: "last hour", outOf: "2.1M requests" }), }); return ( <div className="grid gap-5 sm:grid-cols-3"> <EmptyPanel title="Checkout errors" state={{ kind: "never", onConnect: () => console.log("open the source picker") }} /> <EmptyPanel title="Checkout errors" state={middle} /> <EmptyPanel title="Checkout errors" state={{ kind: "zero", window: "last hour", outOf: "2.1M requests" }} /> </div> );}
What it renders. Identical markup in both panes, with only the token values changing.
Screenshots are grouped by product and include the capture date.
Dashed boxes highlight this pattern. Notes appear below each screenshot and when you hover over a box.
Anomalies / Anomaly advisor September 11, 2026Netdata Agent, Anomaly advisor (public registry node, signed out)medium · dark · desktop-web
Netdata shows the share and count of measurements flagged as unusual in separate charts. This provides a compact overview across many metrics rather than drawing an expected range on each one. People then select a time period to see which metrics contributed. The empty detail panel explains that a selection is needed before results will appear.
Anomaly bandA separate anomaly-rate chart summarizes many metrics.
Linux node / fleet overview September 9, 2026Grafana Play (signed out; no version string exposed)medium · dark · desktop-web
The data source was returning no measurements when this overview was captured. A red tile says no metrics were received and suggests checking configuration. The freshness tile says No data, while another panel says No errors. Those messages need clearer distinctions: missing measurements do not establish that a system has no errors.