Xzero UI
Pro library

Reply draftPro

An AI email reply drafter where Claude streams a reply under a received Gmail message. Quick replies steer a fresh draft, tone and length rewrite it with a single crossfade, and you can edit the finished draft before you insert it.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/reply-draft
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/reply-draft

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { ReplyDraft } from "@/components/xzero/reply-draft/reply-draft";

export function Inbox({ email }) {
  return (
    <ReplyDraft
      message={email}
      suggestions={[{ id: "yes", label: "Sounds good" }, { id: "later", label: "Next week" }]}
      model={{ name: "Claude Sonnet 5.5" }}
      generate={async function* ({ message, tone, length, suggestion, signal }) {
        const res = await fetch("/api/reply", { method: "POST", body: JSON.stringify({ id: email.id, tone, length, hint: suggestion?.label }), signal });
        const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
        for (;;) { const { value, done } = await reader.read(); if (done) return; yield value; }
      }}
      onInsert={draft => composer.insert(draft)}
    />
  );
}

API reference

ReplyDraft

A received email with an AI reply under it: quick reply chips, a fast streaming draft, tone and length rewrites, Regenerate and Insert. With no props it plays a simulated tour.

PropTypeDefaultDescription
messageReplyDraftMessagereplyDraftSampleMessageThe received email: from { name, email, avatar? }, to?, subject, body (blank lines split paragraphs) and receivedAt.
suggestionsReplyDraftSuggestion[]3 sample quick repliesQuick replies ({ id, label }) shown as chips above the draft. Selecting one streams a fresh draft steered by it. Pass [] to hide the row.
defaultSuggestionstring–Id of the quick reply that steers the first draft. Defaults to the first suggestion.
onSuggestionSelect(suggestion: ReplyDraftSuggestion) => void–Called when a quick reply chip is chosen.
modelReplyDraftModel{ name: "Claude Sonnet 5.5" }Model name and an optional 16px mark shown on the draft. The Claude mark is the default.
tone"friendly" | "concise" | "formal"–Controlled tone. Changing it rewrites a finished draft with a crossfade, or restarts one that is still streaming.
defaultTone"friendly" | "concise" | "formal""friendly"Initial tone when uncontrolled.
onToneChange(tone: ReplyDraftTone) => void–Called when the reader picks a tone.
length"short" | "medium" | "long"–Controlled length. Works like tone.
defaultLength"short" | "medium" | "long""medium"Initial length when uncontrolled.
onLengthChange(length: ReplyDraftLength) => void–Called when the reader picks a length.
generate(request: ReplyDraftRequest) => Promise<string> | AsyncIterable<string>simulateReplyDraftWrites the draft. The request carries message, tone, length, suggestion, mode ("draft" or "rewrite"), previous (the current text with edits) and an AbortSignal. Return the whole text, which is streamed locally in chunks, or yield chunks from your model stream. Throw to show the error state.
onInsert(draft: string, meta: { tone, length, suggestion? }) => void | Promise<void>–Receives the final draft, including the reader's edits. While a returned promise is pending, Insert shows a spinner. Without it, Insert is simulated and announces that nothing was sent.
onDraft(draft: string, meta: { tone, length, suggestion? }) => void–Called each time a draft or rewrite finishes.
autoStartbooleantrueStart the first draft when the card scrolls into view. When false, the draft box shows an empty state with a Draft reply button.
autoPlaybooleantrue without generateTours tones, length, a second quick reply and Insert on a loop. Stops for good on the first pointer or key press inside the card and pauses off screen.
classNamestring–Class on the outer wrapper (max width 600px, fills narrower containers).

Keyboard interactions

KeysAction
TabMoves through quick replies, the draft text, tone, length, Regenerate and Insert.
Arrow keysInside Tone or Length, move the selection and rewrite the draft. Home and End jump to the first and last option.
Enter / SpaceActivates a quick reply or button.
Cmd / Ctrl + EnterInserts the finished draft from anywhere inside the card.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in ai

Guidance for AI tools

Reply draft: An AI email reply drafter where Claude streams a reply under a received Gmail message. Quick replies steer a fresh draft, tone and length rewrite it with a single crossfade, and you can edit the finished draft before you insert it. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components