KQ-1342 · MAIL/CLIENT
Mail Client
A complete three-pane mail screen built around mail-inbox and bin-lid: mailboxes whose current-folder pill travels on snap, the inbox list with its swipe triage, and a reading pane where a thread arrives from 4px on glide, earlier messages open on a measured glide, a quick reply grows on glide and lands on snap, Archive and Snooze leave an Undo toast that lands on recoil, and Delete drops the thread in the bin until its undo ring drains. `folders` keeps the mailboxes as a pane, an icon rail or a drawer that slides over the list on glide; below 640px the panes become a stack whose pushes slide in on glide over a dimming, shifting screen, and a swipe from a pushed screen's left edge follows the finger 1:1 and goes back when the projected throw passes halfway. Back, Alt+← and Escape go back, / searches, c composes, and the list keeps its own j, k, x, e, b, s, u and z.
Labels
6.2 GB of 15.0 GB used
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.
No message open
Pick a thread to read it here. j and k move, Enter opens.
4 unread · swipe a row to triage
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/mail-client
Props
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Row height and type size in the list, and the reading pane's type.
- preview
- number
- default 1
- Lines of message preview under each subject in the list, 0 to 2.
- folders
- "pane" | "rail" | "drawer"
- default "pane"
- How the mailboxes live beside the list on wide screens: a labelled pane, a 60px icon rail with badges, or a drawer behind the menu button. A phone always stacks them.
- threads / defaultThreads
- MailThread[]
- default defaultMailClientThreads
- Every thread in every folder (mail-inbox's type). Controlled or uncontrolled.
- onThreadsChange
- (threads: MailThread[]) => void
- default —
- Fires from the swipe, key, button, reply or delete 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 —
- A mailbox was chosen.
- open / defaultOpen
- string | null
- default null
- The thread in the reading pane. Controlled or uncontrolled.
- onOpenChange
- (id: string | null) => void
- default —
- Fires from the row, the arrows, the back swipe or the action that opened or closed a thread.
- account
- MailPerson
- default defaultMailClientAccount
- Whose mail it is: shown at the head of the mailboxes, and the sender of replies.
- storage
- { used: number; total: number }
- default { used: 6.2e9, total: 15e9 }
- The mailbox's storage in bytes, for the meter under the mailboxes.
- now
- Date | number
- default defaultMailClientNow
- The moment times are read against, so server and browser agree.
- zoneOffset
- number
- default 0
- Minutes east of UTC that times are shown in.
- onSend
- (draft: MailDraft) => void
- default —
- A reply (with its threadId) or a new message from Compose was sent.
- onArchive
- (ids: string[]) => void
- default —
- Threads sent to the archive, from the list or the reading pane.
- onSnooze
- (ids: string[], until: number) => void
- default —
- Threads snoozed, and until when (epoch ms).
- onDelete
- (ids: string[]) => void
- default —
- Threads deleted, once the bin's undo window ran out.
- onStarChange
- (id: string, starred: boolean) => void
- default —
- A thread was starred or unstarred.
- onReadChange
- (ids: string[], unread: boolean) => void
- default —
- Threads marked read by opening them, or unread from the list or the pane.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholder rows; error offers Retry.
- onRetry
- () => void
- default —
- Retry was pressed after the mail failed to load.
- label
- string
- default "Mail"
- The screen's accessible name.
- sound
- boolean
- default false
- Play the slides, stars and sends, only for the visitor's own actions; the list and the bin play theirs too.
- disabled
- boolean
- default false
- Read only: nothing can be moved, sent or deleted.
- className
- string
- default —
- Classes for the root. It is 560px tall by default; pass a height class to change it.
| Prop | Type | Default | Description |
|---|---|---|---|
| density | "compact" | "cozy" | "roomy" | "cozy" | Row height and type size in the list, and the reading pane's type. |
| preview | number | 1 | Lines of message preview under each subject in the list, 0 to 2. |
| folders | "pane" | "rail" | "drawer" | "pane" | How the mailboxes live beside the list on wide screens: a labelled pane, a 60px icon rail with badges, or a drawer behind the menu button. A phone always stacks them. |
| threads / defaultThreads | MailThread[] | defaultMailClientThreads | Every thread in every folder (mail-inbox's type). Controlled or uncontrolled. |
| onThreadsChange | (threads: MailThread[]) => void | — | Fires from the swipe, key, button, reply or delete 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 | — | A mailbox was chosen. |
| open / defaultOpen | string | null | null | The thread in the reading pane. Controlled or uncontrolled. |
| onOpenChange | (id: string | null) => void | — | Fires from the row, the arrows, the back swipe or the action that opened or closed a thread. |
| account | MailPerson | defaultMailClientAccount | Whose mail it is: shown at the head of the mailboxes, and the sender of replies. |
| storage | { used: number; total: number } | { used: 6.2e9, total: 15e9 } | The mailbox's storage in bytes, for the meter under the mailboxes. |
| now | Date | number | defaultMailClientNow | The moment times are read against, so server and browser agree. |
| zoneOffset | number | 0 | Minutes east of UTC that times are shown in. |
| onSend | (draft: MailDraft) => void | — | A reply (with its threadId) or a new message from Compose was sent. |
| onArchive | (ids: string[]) => void | — | Threads sent to the archive, from the list or the reading pane. |
| onSnooze | (ids: string[], until: number) => void | — | Threads snoozed, and until when (epoch ms). |
| onDelete | (ids: string[]) => void | — | Threads deleted, once the bin's undo window ran out. |
| onStarChange | (id: string, starred: boolean) => void | — | A thread was starred or unstarred. |
| onReadChange | (ids: string[], unread: boolean) => void | — | Threads marked read by opening them, or unread from the list or the pane. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholder rows; error offers Retry. |
| onRetry | () => void | — | Retry was pressed after the mail failed to load. |
| label | string | "Mail" | The screen's accessible name. |
| sound | boolean | false | Play the slides, stars and sends, only for the visitor's own actions; the list and the bin play theirs too. |
| disabled | boolean | false | Read only: nothing can be moved, sent or deleted. |
| className | string | — | Classes for the root. It is 560px tall by default; pass a height class to change it. |