Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React Empty State Component: First Use, No Results, Errors
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Empty State Component: First Use, No Results, Errors
Component guides
Component guides

React Empty State Component: First Use, No Results, Errors

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 8 min read

On this page

0%
  1. What should a React empty state component show?
  2. Which kinds of empty state does an app need?
  3. How do I write empty state copy that moves people forward?
  4. How do I build empty state UI in React?
  5. Should I show a skeleton or an empty state while data loads?
  6. How do I handle a React table empty state?
  7. How do I make an empty state accessible?
  8. How does an empty state behave on a phone?
  9. How do I translate the default copy once for the whole app?
  10. How do I add it to my project?
  11. FAQ
    1. What is an empty state in UI design?
    2. How do I show an empty state in a React table?
    3. Should I show a spinner, a skeleton or an empty state while data loads?
    4. How do I make an empty state accessible?
    5. Is the Wingo UI Empty State free?

TL;DR

A React empty state component should tell people which kind of empty they are looking at (first use, no results, an error, no access, offline) and give them one action that fixes it. Show a skeleton while data loads, check filters before you show a first-run message, and announce no-results changes to screen readers. The free Wingo UI Empty State ships six presets, a retry button that waits for your promise, and actions that stack on a phone.

Published Oct 19, 2026

A list that has nothing to show is still a screen, and someone is looking at it. A new user sees an empty client list and wonders where to start. A search for "Tulsa" returns nothing and they wonder if search is broken. An API times out and the page looks exactly like the empty client list. A React empty state component is the small piece of UI that tells these situations apart and gives each one a way forward. This guide covers the types of empty state, the copy and actions that work for each, how to wire them in a list and a table, and the accessibility and phone details.

What should a React empty state component show?

An icon or illustration, a title, a short description and one or two actions. The Nielsen Norman Group article on empty states (opens in a new tab) (Kate Kaplan, September 2021) gives them three jobs: communicate system status, provide learning cues and offer direct pathways to the key task. Each part of the anatomy maps to one of those jobs:

  • The tile or illustration marks the area as intentionally empty. It is decorative, so screen readers skip it.
  • The title names what is missing. "No clients yet" tells people more than "Nothing here".
  • The description says why, or what will show up here, in one or two sentences.
  • The actions fix it: add the first item, clear the filters, try again, request access.

The harder part is picking the right copy, because "empty" has several causes and each needs a different message.

Which kinds of empty state does an app need?

Six kinds cover almost every screen: first use, empty, no results, error, no access and offline. The PatternFly empty state guidelines (opens in a new tab) (checked October 2026) list overlapping types such as getting started, no results, no access and back-end failure, and the Wingo UI Empty State ships them as presets that set the icon, tone and default copy at once:

PresetWhen it showsExample titlePrimary action
first-runThe feature has never been usedPost your first listingAdd listing
emptyThe user cleared everything, or there is nothing yetNo clients yetAdd client
no-resultsA search or filter hides every itemNo clients match "Tulsa"Clear filters
errorThe data could not loadWe could not load the invoicesTry again
no-permissionThe user lacks accessYou do not have access to ReportsRequest access
offlineThe device lost its connectionYou are offlineTry again, or refetch on the browser's online event

The order of the checks matters more than the visuals. An active filter means the list is filtered, and the copy has to say so. Telling someone with 200 clients to "Add your first client" because their search matched nothing makes them think their data is gone.

Switch the preset in the playground to see each one, then pick error and press the retry button.

Switch the preset to swap icon, tone and copy at once. On the error preset, press Try again: the button spins until the promise settles
$ npx wingo-ui@latest add empty-state
FreeEmpty State docs

How do I write empty state copy that moves people forward?

State what is missing, then give one clear next step. These are the rules we follow, most of them shared with the PatternFly guidelines linked above:

  • Name the object. "No invoices yet" beats "No data". For creation states, "No X yet" reads as a starting point while "No X found" reads as a failure.
  • Echo the query in no-results. A title like No clients match "Tulsa" proves the search ran and shows a typo at a glance.
  • Say what the user can do next. One sentence. Save the tutorial for onboarding.
  • One primary action. A second, quieter action is fine (Import CSV next to Add client). Three buttons is a menu.
  • No blame and no exclamation points. "Check your connection and try again" instead of "You entered an invalid search!".
  • Match the action to the cause. Retry for a timeout, Request access for a 403, Clear filters for no results. A retry button on a permissions error just fails again.

A few empty state design examples, before and after:

StateVagueSpecific
First useNothing herePost your first listing. Buyers in your city see it within minutes.
No resultsNo results foundNo clients match "Tulsa". Check the spelling or search another city.
ErrorError 503We could not load the invoices. Check your connection and try again.
No accessForbiddenYou do not have access to Reports. Ask Ryan Brooks, the workspace admin.

How do I build empty state UI in React?

Install the component, then branch on the state of your data in a fixed order: loading, error, filtered with no results, no data at all, and finally the list. Skip a check and the empty state UI React renders says the data is gone when it is only loading or filtered.

bash
npx wingo-ui@latest add empty-state skeleton
tsx
"use client";
import { FileUp, UserPlus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { EmptyState } from "@/components/ui/empty-state";
import { SkeletonList } from "@/components/ui/skeleton";
type Client = { id: string; name: string; city: string };
type ClientsViewProps = {
status: "loading" | "error" | "ready";
clients: Client[];
query: string;
onClearSearch: () => void;
onRetry: () => Promise<void>;
onAdd: () => void;
};
export function ClientsView({ status, clients, query, onClearSearch, onRetry, onAdd }: ClientsViewProps) {
if (status === "loading") return <SkeletonList rows={5} label="Loading clients" />;
if (status === "error") {
return (
<EmptyState
preset="error"
title="We could not load your clients"
description="Check your connection and try again."
onRetry={onRetry}
live
/>
);
}
// a filtered list is not an empty account: check the query first
if (clients.length === 0 && query) {
return (
<EmptyState
preset="no-results"
title={`No clients match "${query}"`}
description="Check the spelling or search by city instead."
live
actions={
<Button variant="outline" onClick={onClearSearch}>
Clear search
</Button>
}
/>
);
}
if (clients.length === 0) {
return (
<EmptyState
preset="first-run"
variant="dashed"
title="No clients yet"
description="Add your first client or import the list from a CSV."
actions={
<>
<Button variant="outline" leftIcon={<FileUp />}>
Import CSV
</Button>
<Button leftIcon={<UserPlus />} onClick={onAdd}>
Add client
</Button>
</>
}
/>
);
}
return (
<ul className="flex flex-col">
{clients.map((client) => (
<li key={client.id} className="flex min-h-12 items-center border-b border-foreground/10">
{client.name}, {client.city}
</li>
))}
</ul>
);
}

The props that matter:

  • preset picks the icon, tone and default copy. title, description, icon and badge override any piece; pass null to hide one.
  • actions takes your buttons. Put the primary action last in the DOM: it sits at the end of the row on wide screens and on top when the row stacks.
  • onRetry adds an outline Try again button. If it returns a promise, the button shows a loading state until the promise settles, and a rejection only stops the spinner, so you decide what the next state is.
  • size is sm inside a table cell, list or popover, md in a panel (the default) and lg for a whole page. variant is plain, dashed (first-run areas, drop zones) or surface (a raised panel).
  • illustration replaces the icon tile with an image or SVG, and align="start" left-aligns everything for cards and sidebars.

Should I show a skeleton or an empty state while data loads?

A skeleton, every time the request is still in flight. The empty state belongs to the moment the response arrives with zero items. If you render "No clients yet" before the fetch resolves, every page load flashes a message that says the data is gone, then replaces it. The Skeleton ships list, table, card and profile shapes (SkeletonList, SkeletonTable, SkeletonCard, SkeletonProfile) with a hidden "Loading" label for screen readers, so the loading state has the same footprint as the content it stands in for. The React skeleton loader guide covers shapes, layout shift and when a spinner is the better choice.

How do I handle a React table empty state?

Render the state in place of the rows, with separate copy for a table that has no rows and one whose search or filters hide every row. A plain HTML table needs one full-width cell so the state does not squeeze into the first column:

tsx
<tbody>
{invoices.length === 0 ? (
<tr>
<td colSpan={4} className="p-0">
<EmptyState size="sm" preset="empty" title="No overdue invoices" description="Every client paid on time." />
</td>
</tr>
) : (
invoices.map((invoice) => <InvoiceRow key={invoice.id} invoice={invoice} />)
)}
</tbody>

The Data Table handles the whole React table empty state flow itself. It shows skeleton rows while loading is true, an error state with Try again when you pass error and onRetry, an empty state when there is no data, and a no-results state with a Clear filters button when the search or filters hide every row. You replace the defaults with emptyState and noResultsState:

tsx
<DataTable
data={clients}
columns={columns}
caption="Clients"
loading={isLoading}
error={loadError ? "The server did not respond. Try again in a moment." : undefined}
onRetry={refetch}
emptyState={
<EmptyState
preset="first-run"
size="sm"
title="No clients yet"
description="Add your first client to send invoices and track balances."
actions={
<Button size="sm" leftIcon={<UserPlus />}>
Add client
</Button>
}
/>
}
/>

By default it renders the state alone, without column headers spread over nothing; showHeaderWhenEmpty keeps the headers. The Data Table is a Pro component (pricing); the Empty State inside it is free. On a phone the table turns into cards, a pattern explained in the responsive table guide.

How do I make an empty state accessible?

Announce states that follow a search or a failed load, keep the heading outline, hide the icon from screen readers and move focus when the state goes away:

  1. Announce states that appear after an action. WCAG 2.2 success criterion 4.1.3 Status Messages (opens in a new tab) (level AA) names "No results returned" as an example of a status message that assistive technology should present without moving focus. The live prop adds role="status" with a polite live region, so a screen reader reads the title and description once when the state shows up. Use it for no-results and errors, not for a static first-run page.
  2. Keep the heading outline intact. The title is an h3 by default; set titleAs to h2, h4 or p to fit the page.
  3. Leave the icon decorative. The tile and the illustration render with aria-hidden, so the title carries the meaning.
  4. Move focus when the state disappears. If Clear filters unmounts the empty state, focus would fall to the body. Send it back to the search input, as the component's no-results example does with a ref.

How does an empty state behave on a phone?

Its actions stack full width, primary on top, so they fit a thumb and a narrow column. The Empty State is a container query: when it is about 400px wide or less and has two or more actions, the row stacks. That rule follows the width of the component, so a narrow card on a desktop stacks too. The lg size drops to the md spacing below 640px, and Buttons get a 44px touch target on a coarse pointer. The entrance (the tile scales in, then the text and actions rise one after another) is reduced to a fade when the user asks for reduced motion, and server-rendered markup already shows the final state.

The error preset at size sm: press Try again and watch the button wait for the promise before the state changes
$ npx wingo-ui@latest add empty-state
FreeEmpty State docs

How do I translate the default copy once for the whole app?

Pass presets through UIProvider defaults. Each preset's icon, tone, title, description and badge merge over DEFAULT_EMPTY_STATE_PRESETS, so you set your house style once instead of on every screen:

tsx
import { UIProvider } from "@/lib/ui-config";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<UIProvider
defaults={{
"empty-state": {
presets: {
error: { title: "That did not load", description: "Check your connection and try again." },
"no-permission": { description: "Ask a workspace admin to give you access." },
},
},
}}
>
{children}
</UIProvider>
);
}

Explicit props still win, so a single screen can keep its own copy.

How do I add it to my project?

Run npx wingo-ui@latest add empty-state. The CLI copies the source into components/ui, adds the Button and the motion, tone and config helpers it imports, and installs motion, lucide-react and @radix-ui/react-slot. For full-page versions (404, maintenance, offline), see the React 404 page tutorial. More single-component deep dives live in the component guides category, and the React form components guide applies the same copy, focus and phone rules to every form control.

Components in this post

  • Empty State

    A React empty state for lists, tables, searches and pages with nothing to show yet: an icon tile, a title, a description and actions.

    Free
  • Skeleton

    A React skeleton loader shaped like the content on its way: a shimmering block, text lines and presets for cards, lists and tables.

    Free
  • Data Table

    A React data table on TanStack Table: search, filters, sorting, bulk actions, pinning, resizing, virtualization and cards on phones.

    Pro

FAQ

What is an empty state in UI design?

An empty state is what a list, table, search or page shows when it has no content to display. A good one says why it is empty and offers the next step, such as adding the first item, clearing a filter or retrying a failed load.

How do I show an empty state in a React table?

Render it in place of the rows once loading has finished and the data array is empty, with different copy for no data and for filters that match nothing. In a plain HTML table, put it in a single cell with colSpan set to the column count; the Wingo UI Data Table does this for you through its emptyState and noResultsState props.

Should I show a spinner, a skeleton or an empty state while data loads?

Show a skeleton or a spinner while the request is in flight, and the empty state only after the response arrives with zero items. Showing the empty state first makes people think their data is gone for a moment.

How do I make an empty state accessible?

Keep the icon decorative, give the title a heading level that fits the page outline, and use role="status" for states that appear after a search or filter change so screen readers announce them. If a button that clears the state unmounts it, move focus back to the search field.

Is the Wingo UI Empty State free?

Yes. The Empty State and the Skeleton are free components. The Data Table, which uses the Empty State for its no data, no results and error states, is a Pro component.

  • Empty states
  • UX writing
  • Accessibility
  • Tables
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

React Skeleton Loader: Real Shapes, No Layout Shift

Build a React skeleton loader shaped like the real content, pick a spinner when a skeleton is wrong, and swap in the data without any layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Component guides

React Virtualized Table: 100k Rows With TanStack Virtual

Build a react virtualized table that scrolls 100,000 rows with TanStack Virtual, keeps a sticky header, and holds keyboard focus and selection as rows unmount.

SRSerban Rusu·Oct 16, 2026·12 min read
Component guides

shadcn Registry: Add Third-Party Components Safely

Use a shadcn registry from another library in your shadcn/ui project: namespaced registries, auth headers, checking what it writes, and the files that clash.

SRSerban Rusu·Oct 13, 2026·8 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI