Empty state

Free · MITReact Native · Expo

A designed empty, error or offline state: a tinted disc pops in, its line art draws itself stroke by stroke with the highlight landing last, then the title, description and actions rise in.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/empty-state.json

Then run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/empty-state.

Usage

import { EmptyState } from "@/components/goodui-native/empty-state/empty-state";

export function NoBookings({ onBook }: { onBook: () => void }) {
  return (
    <EmptyState
      preset="no-bookings"
      description="Classes you book show up here with a reminder an hour before."
      action={{ label: "Find a class", onPress: onBook }}
    />
  );
}

When to use

Use it for
  • A list, inbox, calendar or search with nothing to show: ListEmptyComponent of a FlatList.
  • A screen that failed to load or cannot load offline, with a retry.
  • A first-run screen that explains what will appear and offers the first action.
Reach for something else
  • While content is loading: use skeleton.
  • A problem with part of a screen that otherwise works: use banner.
  • Celebrating a finished task: use success-check.

Examples

Search with no results, inside a list

<FlatList
  data={results}
  renderItem={renderRow}
  ListEmptyComponent={
    <EmptyState
      preset="no-results"
      size="sm"
      title={`No results for "${query}"`}
      secondaryAction={{ label: "Clear filters", onPress: clearFilters }}
    />
  }
/>

Error with an async retry

<EmptyState preset="error" action={{ label: "Try again", onPress: () => refetch() }} secondaryAction={{ label: "Contact support", onPress: openSupport }} />

Your own art

<EmptyState illustration={<Image source={require("./inbox.png")} style={{ width: 120, height: 120 }} />} title="Inbox zero" description="Nice work." />

API reference

EmptyState

PropTypeDefault
preset
Ready-made art and copy for the common cases. Your `title` and `description` replace the copy.
"error" | "offline" | "no-results" | "no-messages" | "no-bookings"—
title
What is empty, in a few words. Required without a preset.
string—
description
What to do about it, in one or two short sentences.
string—
illustration
Your own art (an image, an svg) instead of the preset line art. `false` shows no art.
ReactNode | false—
tone
Color of the art's highlight stroke and the disc behind it. Presets pick one (error is danger, offline warning).
"success" | "warning" | "danger" | "accent" | "neutral"—
size
`md` for a whole screen or a large panel, `sm` inside a list, a card or a sheet.
"sm" | "md""md"
action
The one way forward, a primary button.
EmptyStateAction—
secondaryAction
A quieter second option, a ghost button.
EmptyStateAction—
animated
Draw the art in and let the text rise. `false` shows everything at once. Default `true`.
booleantrue
labels
Overrides for the built-in preset copy; a GooduiLocaleProvider pack sets it app-wide.
Partial<EmptyStateLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

EmptyStateIllustration

PropTypeDefault
name*
Which line art.
"error" | "offline" | "no-results" | "no-messages" | "no-bookings"—
size
Width and height in points. Default 120.
number120
tone"success" | "warning" | "danger" | "accent" | "neutral"—
animated
Draw the strokes in, one after another. Default `true`.
booleantrue
playKey
Change it to draw the art again.
string | number—

Motion and haptics

  • The disc behind the art scales from 0.6 on the bouncy spring; the strokes draw in order (each in its own overlapping window over about a second), neutral strokes first and the highlight strokes last.
  • Title, description and actions rise 10 pt and fade in on the smooth spring, staggered 70 ms, after the art has started.
  • The art draws again and the text rises again when the preset, title or description changes, for example a new search query.
  • Reduced motion: no drawing and no rise; the art and text fade in.
  • animated={false} shows everything at once.
  • Haptic: None of its own; action buttons tap on press and play success or error for returned promises.
  • Each stroke is one animated svg path driven by one shared progress value on the UI thread; a preset has 5 to 10 strokes.

Accessibility

  • The title has the header role. The art is hidden from screen readers (no-hide-descendants, aria-hidden).
  • Actions are goodui Buttons: role button, busy while a returned promise is pending, with announced success and error.
  • Text is centered with a 360 pt measure; long titles wrap.

Notes for AI

  • Pick a preset for the common cases and override title and description with your own copy; the preset decides the art and the tone (error is danger, offline warning).
  • Use size='sm' inside lists, cards and sheets; md for a whole screen.
  • Pass at most one action (primary button) and one secondaryAction (ghost button). onPress may return a promise.
  • illustration={false} removes the art; any ReactNode replaces it. EmptyStateIllustration renders the line art alone.