Data States
Every MetricUI component handles four data states out of the box: loading, empty, error, and stale. No conditional rendering needed.
Overview
Instead of wrapping components in loading spinners or error boundaries manually, pass the state directly to any component. MetricUI renders the appropriate skeleton, message, or indicator automatically.
Loading
Shows a skeleton placeholder that matches the component's layout. The skeleton uses CSS variables for colors — no hardcoded grays.
<KpiCard title="Revenue" value={0} format="currency" loading />
<AreaChart data={[]} title="Trends" loading />
<DataTable data={[]} columns={columns} loading />Empty
Displayed when there's no data to show. Customizable message, icon, and action.
<KpiCard
title="Revenue"
value={0}
empty={{
message: "No revenue data for this period",
icon: <BarChart className="h-8 w-8" />,
action: { label: "Change period", onClick: () => {} },
}}
/>
// Or just a simple message
<AreaChart data={[]} empty={{ message: "No data available" }} />Error
Shows an error message with an optional retry button. In dev mode, includes component name and stack trace.
<KpiCard
title="Revenue"
value={0}
error={{ message: "Failed to load data", onRetry: () => refetch() }}
/>Stale
Shows a subtle indicator that the data is outdated. The component still renders its value — just with a stale badge.
<KpiCard
title="Revenue"
value={142300}
format="currency"
stale={{ message: "Updated 5 min ago" }}
/>Grouped State Prop
Instead of individual props, use the state prop to pass all states at once. This is cleaner when states come from a data-fetching hook.
const { data, isLoading, error } = useSWR("/api/revenue");
<KpiCard
title="Revenue"
value={data?.value ?? 0}
format="currency"
state={{
loading: isLoading,
error: error ? { message: error.message } : undefined,
empty: !data ? { message: "No data" } : undefined,
}}
/>The state prop takes precedence over individual loading/empty/error/stale props.
Global Defaults
Set default empty and error state templates via MetricProvider:
<MetricProvider
emptyState={{ message: "No data available" }}
errorState={{ message: "Something went wrong" }}
>
{/* All components inherit these defaults */}
</MetricProvider>