# Inbox screen

> A complete messaging inbox: a large title that collapses into the bar, a search field, conversation rows with avatars, previews, times and unread badges, swipe to mute, archive or delete with Undo, and pull to refresh where new conversations drop in at the top and push the list down.

- Type: Component (screens)
- Access: Pro
- Page: https://goodui.dev/native/components/inbox-screen
- Markdown: https://goodui.dev/native/components/inbox-screen.md
- Registry item: https://goodui.dev/native/r/inbox-screen.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native inbox screen, messages list screen, chat list, conversation list, expo messaging app template, swipe to archive, unread badge list

## When to use

- The conversation list of a messaging, support or marketplace app.
- Any list of threads with unread counts that people triage with swipes: DMs, support tickets, team chats.

## When not to use

- Notifications or activity: use notifications-screen, which groups by day and carries inline actions.
- Email with subjects and long previews: compose swipe-actions and list rows yourself; this row is built for one-line names and short previews.
- Thousands of conversations: the rows are plain views, so page the data (or move the row into a FlatList) past a few hundred.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add @goodui-native-pro/inbox-screen
npx expo install --fix
```

Pro: add `"@goodui-native-pro": { "url": "https://goodui.dev/native/r/pro/{name}.json", "headers": { "Authorization": "Bearer ${GOODUI_LICENSE_KEY}" } }` to the `registries` of components.json and put your license key in .env.local.

The component lands in `components/goodui-native/inbox-screen/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs avatar, badge, empty-state, large-title-header, locale, pull-to-refresh, search-bar, swipe-actions, toast.

### Setup

- Wrap the app in GestureHandlerRootView and ThemeProvider, and render <Toaster /> once inside ThemeProvider.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
// app/(tabs)/messages.tsx
import { useState } from "react";
import { Stack, router } from "expo-router";
import { InboxScreen, type InboxConversation } from "@/components/goodui-native/inbox-screen/inbox-screen";
import { api } from "@/lib/api";

export default function MessagesRoute() {
  const [conversations, setConversations] = useState<InboxConversation[]>(api.cachedConversations());
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <InboxScreen
        conversations={conversations}
        onOpenConversation={(c) => router.push(`/chat/${c.id}`)}
        onRefresh={async () => {
          const fresh = await api.conversations();
          setConversations(fresh);
          return fresh;
        }}
        onArchive={(id) => api.archive(id)}
        onDelete={(id) => api.remove(id)}
        onRestore={(id) => api.restore(id)}
        onMuteChange={(id, muted) => api.mute(id, muted)}
        onCompose={() => router.push("/chat/new")}
      />
    </>
  );
}
```

## Examples

### On its own, with sample conversations

```tsx
<InboxScreen />
```

### Times in German

```tsx
<InboxScreen conversations={conversations} locale="de-DE" title="Nachrichten" />
```

## API reference

### InboxScreen

`import { InboxScreen } from "@/components/goodui-native/inbox-screen/inbox-screen";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `conversations` | `InboxConversation[]` | `sampleConversations` | Conversations to show. The screen keeps its own copy for swipes and undo and takes a new array whenever this one changes, so keep it stable (state or useMemo), not a new literal on every render. |
| `title` | `string` | – | Large title. Default "Messages". Same as `labels.title`. |
| `onOpenConversation` | `(conversation: InboxConversation) => void` | – | A row was tapped. The row is marked read on screen at the same time. |
| `onRefresh` | `() => Promise<InboxConversation[] \| void> \| InboxConversation[] \| void` | – | Pull to refresh. Resolve with the fresh list and every conversation that was not on screen before drops in at the top; resolve with nothing to keep the list. A rejection shows the pull indicator's error. |
| `onArchive` | `(id: string) => void` | – | A conversation was archived (swipe left, or a long swipe). A toast then offers Undo for 4 seconds. |
| `onDelete` | `(id: string) => void` | – | A conversation was deleted from the swipe actions. A toast then offers Undo for 4 seconds. |
| `onRestore` | `(id: string) => void` | – | Undo was pressed after an archive or delete: the conversation is back on screen. |
| `onMuteChange` | `(id: string, muted: boolean) => void` | – | Mute was toggled from the swipe actions (swipe right). |
| `onCompose` | `() => void` | – | Shows a compose button in the bar. |
| `locale` | `string` | – | BCP 47 locale for times and dates. Defaults to the device locale. |
| `labels` | `Partial<InboxScreenLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- Each conversation is one button whose label starts with the name, then the preview, the time, the unread count and 'muted', so the web name starts with the visible text.
- Mute, archive and delete are in each row's screen reader actions menu, so nobody has to swipe.
- The search field, the compose button ('New message') and the pull to refresh strip (a Refresh button on the web and with a screen reader) are reachable by keyboard and screen reader.
- New conversations from a refresh are announced ('New message from Maya Patel').
- Unread badges are decorative because the row label already says the count.

## Motion

- The large title fades into the bar as the list scrolls, with the hairline arriving last.
- Pull to refresh: the drop stretches, snaps round past the threshold, spins and ends on a check.
- New conversations after a refresh (or one brought back with Undo): the row's height opens on the bouncy spring so the rows below glide down, while the row drops in from above and fades in.
- Archive and delete slide the row out and collapse its height before it leaves.
- Reduced motion: new rows take their place at once and fade in; the title cross-fades without moving.

## Haptics

- tap past the pull threshold, success or error when the refresh settles (pull-to-refresh)
- select when a swipe crosses the long-swipe threshold, tap when a swipe action runs, delete for Delete (swipe-actions); notify for the toast, undo on its Undo
- tap on the compose button

## Performance

- Scroll, pull, swipes and the drop-in run on the UI thread; React renders when the list or the query changes.
- Rows are plain views inside the pull to refresh scroll view: fine for a few hundred conversations; page beyond that.

## Notes for AI

- Pass conversations with Date objects in time; the screen sorts them newest first and formats times with Intl (time today, 'Yesterday', weekday this week, day and month before).
- Keep the conversations array stable (state or useMemo). A new array replaces what is on screen, including rows the person just archived.
- onRefresh should resolve with the new list (the screen diffs ids to animate new rows) or with nothing to keep the list.
- Archive and delete remove the row on screen and call onArchive or onDelete right away; Undo puts it back and calls onRestore. Do the server call in those callbacks.
- Tapping a row marks it read on screen and calls onOpenConversation; navigate there.
- Render it full-bleed with the navigator header hidden (headerShown: false); it handles the safe area itself.
- It needs <Toaster /> once in the app for the Undo toast.

## Related

- [Chat screen](https://goodui.dev/native/components/chat-screen.md): A complete one-to-one chat thread: a header with the contact's avatar, presence and call buttons, messages grouped by day and by sender, receipts that tick forward, typing dots, long-press reactions and a composer that rides the keyboard; a sent message springs up out of the composer and ticks from clock to read.
- [Notifications screen](https://goodui.dev/native/components/notifications-screen.md): Activity grouped into Today and Earlier with avatars, unread dots and thumbnails, follow requests with Accept and Decline in the row, swipe to dismiss, Mark all read where the dots fold away one after another, a banner that offers to turn on push while it is off, and an empty state when everything is cleared.
- [Swipe actions](https://goodui.dev/native/components/swipe-actions.md): Row actions that unfold with the finger; a long swipe lets the edge action take over and run, and destructive actions collapse the row before it leaves.
- [Pull to refresh](https://goodui.dev/native/components/pull-to-refresh.md): A scroll view whose refresh indicator is a drop that stretches as you pull, snaps round with a bounce and a haptic past the threshold, spins while onRefresh runs and ends on a check or an error.
- [Large title header](https://goodui.dev/native/components/large-title-header.md): An iOS large title that collapses into a compact bar as the content scrolls: the titles cross-fade, the bar fades in, the title stretches when pulled past the top and a half-finished collapse settles on either state.
- [Search bar](https://goodui.dev/native/components/search-bar.md): An iOS-style search field: focusing it shrinks the field while a Cancel button springs in beside it and scope chips cascade in below. Clear button, loading spinner and debounced queries built in.

## Guidance for AI tools

Inbox screen: A complete messaging inbox: a large title that collapses into the bar, a search field, conversation rows with avatars, previews, times and unread badges, swipe to mute, archive or delete with Undo, and pull to refresh where new conversations drop in at the top and push the list down. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
