KQ-1304 · MAIL/INBOX
Mail Inbox
An inbox built for triage: a row's face follows the finger 1:1 and reveals its action from the edge it leaves, archive on a green pigment and snooze on an amber one, arming with a pop on snap at the commit line; a release past it (or a throw, by projection) runs the row out on glide with its release velocity before its height closes, and a short one springs home on snap. Selecting a thread slides a batch toolbar down over the header on snap with a rolling count, opening one fades its unread dot and glides its messages open under the row (or into a reading pane when the inbox is wide), and every archive leaves an Undo toast that lands on recoil. j and k move, x selects, Enter opens, e archives, b snoozes, s stars, u toggles unread, z undoes and Escape backs out.
Labels
j k move · x select · e archive · b snooze · z undo
j and k move between threads, x selects, Enter opens, e archives, b snoozes, s stars, u marks unread, z undoes. Swipe a row right or left for its actions.
4 unread · swipe right to archive, left to snooze
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/mail-inbox
Props
- swipe
- "full" | "reveal" | "off"
- default "full"
- How a swipe commits: past 42% of the row, resting open on its action (a long swipe still commits), or not at all, leaving the hover tools and keys.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Row height and type size: single-line compact rows, cozy, or roomy with larger avatars.
- preview
- number
- default 1
- Lines of message preview under the subject, 0 to 2.
- threads / defaultThreads
- MailThread[]
- default defaultMailThreads
- Every thread in every folder, each a subject and its messages oldest first. Controlled or uncontrolled.
- onThreadsChange
- (threads: MailThread[]) => void
- default —
- Fires from the swipe, key or button that changed the threads, with all of them.
- folder / defaultFolder
- "inbox" | "snoozed" | "archive"
- default "inbox"
- The folder on screen. Controlled or uncontrolled.
- onFolderChange
- (folder: MailFolder) => void
- default —
- Fires when a folder is chosen on the rail or the folder chips.
- selected / defaultSelected
- string[]
- default []
- Selected thread ids. A selection brings in the batch toolbar.
- onSelectedChange
- (ids: string[]) => void
- default —
- Fires from the checkbox, x, select-all or batch action that changed the selection.
- open / defaultOpen
- string | null
- default null
- The open thread: expanded under its row, or in the reading pane once the inbox is 68rem wide.
- onOpenChange
- (id: string | null) => void
- default —
- Fires when a thread opens or closes.
- swipeActions
- { right: MailAction | null; left: MailAction | null }
- default { right: "archive", left: "snooze" }
- Which action each swipe direction runs; null leaves that direction rubber-banding. In the archive or snoozed folder the action that would send a thread where it is moves it back to the inbox.
- now
- Date | number
- default defaultMailNow
- The moment times are read against (09:12, Yesterday, Tue, 25 Sep). Never read from the clock during render.
- zoneOffset
- number
- default 0
- Minutes east of UTC that times are shown in, so the server and the browser draw the same inbox.
- snoozeUntil
- (now: number) => number
- default —
- When a snooze ends, from now. Defaults to the next day at 08:00.
- account
- MailPerson
- default —
- Whose inbox it is: shown on the rail, and their messages read as "me".
- label
- string
- default "Mail"
- The inbox's accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholder rows; error offers Retry.
- onRetry
- () => void
- default —
- Retry was pressed on the error state.
- onArchive
- (ids: string[]) => void
- default —
- Threads were sent to the archive, by swipe, key, hover tool or batch.
- onSnooze
- (ids: string[], until: number) => void
- default —
- Threads were snoozed, and until when.
- onRestore
- (ids: string[]) => void
- default —
- Threads were moved back to the inbox from the archive or a snooze.
- onReadChange
- (ids: string[], unread: boolean) => void
- default —
- Threads were marked read by opening them, or toggled with u or the mail tool.
- onStarChange
- (id: string, starred: boolean) => void
- default —
- A thread was starred or unstarred.
- onReply
- (threadId: string) => void
- default —
- Reply was pressed on an open thread.
- onCompose
- () => void
- default —
- Compose was pressed on the rail.
- onUndo
- (ids: string[]) => void
- default —
- The last archive or snooze was undone from the toast or with z.
- sound
- boolean
- default false
- Play a pop when a swipe arms, a pick or a star lands, and a swish as rows leave. Off unless asked for.
- disabled
- boolean
- default false
- Makes the whole inbox inert.
- className
- string
- default —
- Classes for the root; it is 560px tall by default and lays out by its own width.
| Prop | Type | Default | Description |
|---|---|---|---|
| swipe | "full" | "reveal" | "off" | "full" | How a swipe commits: past 42% of the row, resting open on its action (a long swipe still commits), or not at all, leaving the hover tools and keys. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Row height and type size: single-line compact rows, cozy, or roomy with larger avatars. |
| preview | number | 1 | Lines of message preview under the subject, 0 to 2. |
| threads / defaultThreads | MailThread[] | defaultMailThreads | Every thread in every folder, each a subject and its messages oldest first. Controlled or uncontrolled. |
| onThreadsChange | (threads: MailThread[]) => void | — | Fires from the swipe, key or button that changed the threads, with all of them. |
| folder / defaultFolder | "inbox" | "snoozed" | "archive" | "inbox" | The folder on screen. Controlled or uncontrolled. |
| onFolderChange | (folder: MailFolder) => void | — | Fires when a folder is chosen on the rail or the folder chips. |
| selected / defaultSelected | string[] | [] | Selected thread ids. A selection brings in the batch toolbar. |
| onSelectedChange | (ids: string[]) => void | — | Fires from the checkbox, x, select-all or batch action that changed the selection. |
| open / defaultOpen | string | null | null | The open thread: expanded under its row, or in the reading pane once the inbox is 68rem wide. |
| onOpenChange | (id: string | null) => void | — | Fires when a thread opens or closes. |
| swipeActions | { right: MailAction | null; left: MailAction | null } | { right: "archive", left: "snooze" } | Which action each swipe direction runs; null leaves that direction rubber-banding. In the archive or snoozed folder the action that would send a thread where it is moves it back to the inbox. |
| now | Date | number | defaultMailNow | The moment times are read against (09:12, Yesterday, Tue, 25 Sep). Never read from the clock during render. |
| zoneOffset | number | 0 | Minutes east of UTC that times are shown in, so the server and the browser draw the same inbox. |
| snoozeUntil | (now: number) => number | — | When a snooze ends, from now. Defaults to the next day at 08:00. |
| account | MailPerson | — | Whose inbox it is: shown on the rail, and their messages read as "me". |
| label | string | "Mail" | The inbox's accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholder rows; error offers Retry. |
| onRetry | () => void | — | Retry was pressed on the error state. |
| onArchive | (ids: string[]) => void | — | Threads were sent to the archive, by swipe, key, hover tool or batch. |
| onSnooze | (ids: string[], until: number) => void | — | Threads were snoozed, and until when. |
| onRestore | (ids: string[]) => void | — | Threads were moved back to the inbox from the archive or a snooze. |
| onReadChange | (ids: string[], unread: boolean) => void | — | Threads were marked read by opening them, or toggled with u or the mail tool. |
| onStarChange | (id: string, starred: boolean) => void | — | A thread was starred or unstarred. |
| onReply | (threadId: string) => void | — | Reply was pressed on an open thread. |
| onCompose | () => void | — | Compose was pressed on the rail. |
| onUndo | (ids: string[]) => void | — | The last archive or snooze was undone from the toast or with z. |
| sound | boolean | false | Play a pop when a swipe arms, a pick or a star lands, and a swish as rows leave. Off unless asked for. |
| disabled | boolean | false | Makes the whole inbox inert. |
| className | string | — | Classes for the root; it is 560px tall by default and lays out by its own width. |