Email Draft

Pro

The email an assistant wrote for you, shown as an email instead of a blob of JSON. You read who it goes to, what it says and what is attached, fix it in place, and send it. Once it is gone it folds into a single line you can open again.

Send an email to Dana about the Q3 numbers, cc the finance group.

Here is a draft. Read it over, change anything that reads wrong, and send it when you are happy.

Email sent
To
DDana Whitfield, dana@northwind.co
Cc
MMarco Ruiz, marco@northwind.coPPriya Shah, priya@koalaui.com
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi
  • q3-revenue-breakdown.xlsxXLSX · 180 KB
  • board-deck-q3.pdfPDF · 2.4 MB

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add email-draft

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

A sheet with the email on it: an address block, the subject, the body and the files. Under it, the actions. The root adds the summary line the draft folds into once it is sent or discarded, so you never render it yourself.

import {  EmailDraft,  EmailDraftContent,  EmailDraftHeader,  EmailDraftField,  EmailDraftRecipients,  EmailDraftRecipient,  EmailDraftSubject,  EmailDraftBody,  EmailDraftAttachments,  EmailDraftActions,} from "@/components/ui/email-draft"<EmailDraft>  <EmailDraftContent>    <EmailDraftHeader>      <EmailDraftField>        <EmailDraftRecipients>          <EmailDraftRecipient />        </EmailDraftRecipients>      </EmailDraftField>    </EmailDraftHeader>    <EmailDraftSubject />    <EmailDraftBody />    <EmailDraftAttachments />  </EmailDraftContent>  <EmailDraftActions /></EmailDraft>

Draft#

The resting state: the email as it will arrive. Recipients are chips with a face and a name, the address one hover away. A long Cc list caps itself with max and the rest sit behind a +N that names them on hover and opens them on click. The body keeps every line break the model wrote. Discard sits apart on the left, in the destructive ink but without a fill; Edit and Send pair up on the right.

Email sent
From
JJordi Espinosa, jordi@koalaui.com
To
DDana Whitfield, dana@northwind.co
Cc
MMarco Ruiz, marco@northwind.coPPriya Shah, priya@koalaui.com
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M in revenue, up 18% on Q2. Net retention landed at 112%, and Team seats grew 31%, most of it after the September launch. The full breakdown is in the attached sheet. Happy to walk you through it before Thursday's board call. Thanks, Jordi

Editing#

Edit turns the subject into an Input and the body into an autosizing Textarea, drawn around the words where they already are: the fields grow out into the margin, so not a letter moves. The body takes focus with the caret at the end. Edit and Send roll into Cancel and Done; Escape cancels and puts the text back, Done (or ⌘ Enter) keeps it.

Email sent
To
DDana Whitfield, dana@northwind.co

Sending#

Return a promise from onSend and the draft waits on it: Send holds a spinner at its own width, Edit and Discard step back, and the root is aria-busy. A rejected promise drops back to the draft, with the edits intact.

Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi

Sent#

Once it goes, the draft folds into one line on the same clock as the actions leave: who it went to, and when. View opens the email again where it was, read only, and Hide folds it back. The time is stamped when it sends; pass sentAt to show your server's instead.

Email sent · 2:14 PM
To
DDana Whitfield, dana@northwind.co
Cc
MMarco Ruiz, marco@northwind.coPPriya Shah, priya@koalaui.com
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi
  • q3-revenue-breakdown.xlsxXLSX · 180 KB
  • board-deck-q3.pdfPDF · 2.4 MB

Discarded#

Discard folds the draft the same way, into a muted line with an Undo, so a slip costs one click rather than a new prompt.

Draft discarded
To
DDana Whitfield, dana@northwind.co
Cc
MMarco Ruiz, marco@northwind.coPPriya Shah, priya@koalaui.com
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi

Attachments#

EmailDraftAttachments lays out Koala's Attachment under the body, so a file in a draft looks exactly like a file in the composer or a sent message. Give each one an onOpen or an href to make it open.

Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi
  • q3-revenue-breakdown.xlsxXLSX · 180 KB
  • board-deck-q3.pdfPDF · 2.4 MB

With Tool approval#

When the email is a tool call waiting for a yes, drop EmailDraftActions and let the Tool's approval row decide. Use variant="plain" so the sheet sits in the Tool's frame like any other input, drive editing from the approval's Edit, and map the call's status: running is sending, done is sent, rejected is discarded.

Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi

Send this email to Dana Whitfield?

Variants#

card sets the sheet into a tinted frame with the actions on the frame, the same inset look as Tool and Approval. plain is the sheet alone, for a surface that already frames it.

card
Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi
plain
Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi

Density#

Density tunes the sheet's vertical rhythm only. The side margin stays at 16px in both, because that is the room the edit fields grow into.

comfortable
Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi
compact
Email sent
To
DDana Whitfield, dana@northwind.co
Q3 numbers: revenue up 18%
Hi Dana, Q3 closed at $4.2M, up 18% on Q2. The breakdown is in the attached sheet. Thanks, Jordi

API reference#

EmailDraft

The draft, and the context every part reads. Renders the folded summary line itself and wraps its children in the fold. Every part accepts className, merged last, and every part is droppable.

PropTypeDefaultDescription
status"draft" | "sending" | "sent" | "discarded"-Controlled status. sent and discarded fold the draft into one line.
defaultStatusEmailDraftStatus"draft"Initial status when uncontrolled.
onStatusChange(status: EmailDraftStatus) => void-Called on Send, Discard, Undo, and when a send settles.
onSend(edits: EmailDraftEdits) => void | Promise<unknown>-Called with the fields the person changed. Return a promise and the draft holds sending until it resolves (then sent) or rejects (back to draft); return nothing and it goes straight to sent.
onDiscard() => void-Called when the draft is discarded.
editingboolean-Controlled edit mode. Only takes effect while the status is draft.
defaultEditingbooleanfalseInitial edit mode when uncontrolled.
onEditingChange(editing: boolean) => void-Called on Edit, Done, Cancel and Escape.
onEditsChange(edits: EmailDraftEdits) => void-Called on every keystroke with { subject?, body? }, and with the restored values when an edit is cancelled.
recipientstring-Who the folded line names. Defaults to the first recipient in the to field, and counts the rest.
sentAtDate | string-The time on the sent line. A string is shown as is; a Date is formatted on the client. Stamped automatically when it sends.
variant"card" | "plain""card"card sets the sheet in a tinted frame; plain is the sheet alone, for a Tool or a panel that already frames it.
density"comfortable" | "compact"-The sheet's vertical padding. Inherits the nearest DensityProvider (compact with none).
Data attributeDescription
data-statusThe current status.
data-editingPresent while editing.
data-foldedPresent when sent or discarded.
data-collapsedPresent while the message is folded away (sent and not viewed, or discarded).

EmailDraftContent

The sheet the email is written on: the header, subject, body and attachments. Forwards native <div> props.

EmailDraftHeader

The address block. Draws a hairline under itself that disappears when nothing follows. Forwards native <div> props.

EmailDraftField

One address row: a label column and its value. Forwards native <div> props.

PropTypeDefaultDescription
kind"from" | "to" | "cc" | "bcc"-Names the row (From, To, Cc, Bcc). Recipients in the to row are the ones the folded line names.
labelReact.ReactNode-Overrides the label kind picks.
Data attributeDescription
data-kindThe row's kind.

EmailDraftRecipients

A list of recipient chips that can cap itself. Forwards native <span> props.

PropTypeDefaultDescription
maxnumber-Chips shown before the rest collapse into a +N button. The button lists the hidden names on hover and shows them all on click.

EmailDraftRecipient

One person: avatar and name in a chip, with the address in a tooltip and in the accessible name. Forwards native <span> props.

PropTypeDefaultDescription
namestring-The display name.
emailstring-The address. Shown alone when there is no name.
srcstring-The avatar photo. Initials stand in while it loads or when there is none.

EmailDraftSubject

The subject line. Pass it as a string and it turns into an Input while editing, set so the words do not move. Forwards native <div> props.

EmailDraftBody

The message. Line breaks and blank lines are kept as written. Pass it as a string and it turns into an autosizing Textarea while editing, focused at the end. Forwards native <div> props.

EmailDraftAttachments

The files going with it, as Koala Attachments. Forwards native <div> props.

PropTypeDefaultDescription
variant"default" | "inline""default"Passed to Attachments.

EmailDraftActions

Discard, Edit and Send. While editing they become Cancel and Done, with Discard stepping out of the way; while sending, Send holds a spinner. Folds away once the draft is sent or discarded. Children land between Discard and Edit. Forwards native <div> props.

FAQ#

Because nobody approves an email by reading JSON. The person needs to see what Dana will see: who it goes to, the subject, the words and the files. Email Draft renders exactly that, and the arguments are still yours to keep in the Tool if you want them.