Empty state
Free · MITReact Native · ExpoA 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.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/empty-state.jsonThen 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
- 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.
- 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
| Prop | Type | Default |
|---|---|---|
| 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`. | boolean | true |
| 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
| Prop | Type | Default |
|---|---|---|
| name* Which line art. | "error" | "offline" | "no-results" | "no-messages" | "no-bookings" | — |
| size Width and height in points. Default 120. | number | 120 |
| tone | "success" | "warning" | "danger" | "accent" | "neutral" | — |
| animated Draw the strokes in, one after another. Default `true`. | boolean | true |
| 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.
Related
Loading placeholders whose shimmer is one light passing diagonally over the whole screen, plus presets and a Swap that eases from the skeleton's height to the content's while it cross-fades.
An inline status banner that slides down from under the edge above and pushes the content down, flings away sideways with the finger, and can collapse on its own behind a draining hairline; includes an offline preset.
A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
A success or error moment: the circle springs in, the check draws itself while a ring pulses out and the title rises in; the error variant draws a cross and shakes once. Inline or fullscreen, optionally with confetti.