# Empty State

> What a Screen or list shows when there is nothing to show yet: an icon, a title, a description and an action.

Component · feedback. Registry item `empty-state`, shown in the vega Style. JSON: https://nativecn.dev/r/styles/vega/empty-state.json

## Installation

```sh
npx nativecn-cli@latest add empty-state
```

Also installs: [theme](https://nativecn.dev/docs/theme/theme), [text](https://nativecn.dev/docs/components/text), [icon](https://nativecn.dev/docs/components/icon).

Packages: `lucide-react-native@^1.51.0`, `react-native-svg`.

## Usage

### Empty State demo

An empty inbox with a New message action.

```tsx
import { Inbox, Plus } from "lucide-react-native";

import { Button } from "@/components/button";
import { EmptyState } from "@/components/empty-state";

/** An empty inbox with an action. */
export default function EmptyStateDemo() {
  return (
    <EmptyState
      icon={Inbox}
      title="No messages yet"
      description="When someone writes to you, their message will appear here."
    >
      <Button label="New message" icon={Plus} />
    </EmptyState>
  );
}
```

## Props

| Prop | Type and description |
|---|---|
| `icon` | Lucide icon component, drawn in a muted circle |
| `title` | string (required), read as a heading |
| `description` | string |
| `children` | the action, usually a Button |

## Accessibility

- The title has role heading (the h4 text Variant).
- The icon is decorative and hidden from screen readers.
- The action (usually a Button) brings its own role and name.

## Notes

Centre it in the free space (style={{ flex: 1 }}, or a list's ListEmptyComponent). Padding, gap and icon size come from the empty-state.root and empty-state.icon Style Slots.

## Source

### {components}/empty-state.tsx

```tsx
import type { LucideIcon } from "lucide-react-native";
import type { ReactNode, Ref } from "react";
import { View, type ViewProps } from "react-native";

import { Icon } from "@/components/icon";
import { Text } from "@/components/text";
import { createStyles } from "@/theme";

export type EmptyStateProps = Omit<ViewProps, "children"> & {
  /** A Lucide icon component, drawn in a soft circle above the title. */
  icon?: LucideIcon;
  /** What is empty, e.g. "No messages yet". Read as a heading. */
  title: string;
  /** What the user can do about it. */
  description?: string;
  /** The action, usually a Button. */
  children?: ReactNode;
  ref?: Ref<View>;
};

const useStyles = createStyles((t) => ({
  root: { ...{ padding: t.spacing[8], gap: t.spacing[3] }, alignItems: "center", justifyContent: "center" },
  icon: {
    ...{ width: t.scaleValue(48), height: t.scaleValue(48) },
    borderRadius: t.radius.full,
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: t.colors.muted,
  },
  text: { alignItems: "center", gap: t.spacing[1] },
  action: { marginTop: t.spacing[2] },
}));

/**
 * What a Screen or list shows when there is nothing to show yet: an icon, a title, a short
 * description and an optional action (`children`). The title is a heading. `style` is merged
 * last.
 */
export function EmptyState({
  icon,
  title,
  description,
  children,
  style,
  ...props
}: EmptyStateProps) {
  const styles = useStyles();

  return (
    <View style={[styles.root, style]} {...props}>
      {icon ? (
        <View style={styles.icon}>
          <Icon icon={icon} size="lg" color="mutedForeground" />
        </View>
      ) : null}
      <View style={styles.text}>
        <Text variant="h4" align="center">
          {title}
        </Text>
        {description ? (
          <Text variant="small" color="mutedForeground" align="center">
            {description}
          </Text>
        ) : null}
      </View>
      {children != null ? <View style={styles.action}>{children}</View> : null}
    </View>
  );
}
```
