PixelDosa UI

Draft Mode

The agent prepares everything and nothing leaves until a person sends it.

Draft mode

The agent prepares everything. Nothing leaves until you send it.

Choose what to send. Nothing goes out on its own.

Installation

bash
npx shadcn@latest add @pixeldosa/draft-mode

Requires `cn` at `@/lib/utils`. The component owns only the selection; the held items, the mode and what sending means stay with you.

Usage

"use client";

import { DraftMode } from "@/components/ui/draft-mode";

<DraftMode
  enabled={draftMode}
  onEnabledChange={setDraftMode}
  items={held}
  onSend={send}
  onDiscard={discard}
  onEdit={open}
/>

Why it exists

An approval dialog interrupts once per action, which teaches people to click through it. Draft mode is the opposite: the agent works freely, its output collects in one place, and the person reviews a batch when they choose to. Same protection, far less interruption.

Features

  • Nothing sends itself. Items are chosen with checkboxes, and the button names the count: "Send 3".
  • Irreversible work is marked on the item, and counted in the sentence under the button as the selection changes.
  • "Select all" is a visible step, never a hidden accept-all.
  • Off is explained, not silent. With draft mode off, a still, orange-marked strip says what happens instead and offers to turn it back on.
  • Stale selections can't fire. An item that leaves the list is dropped from the selection, so a batch action can't send something that has already gone.
  • Holds completely still. Everything here is your turn.

Variants

Waiting for review

Draft mode

The agent prepares everything. Nothing leaves until you send it.

Choose what to send. Nothing goes out on its own.

Nothing waiting

Draft mode

The agent prepares everything. Nothing leaves until you send it.

Nothing waiting. Anything the agent prepares will be held here.

Draft mode off

Draft mode

The agent sends email as soon as it finishes drafting.

Draft mode is off. to hold the agent's work for review.

Nothing waiting. Anything the agent prepares will be held here.

Props

PropTypeNotes
enabledbooleanRequired.
onEnabledChange(enabled: boolean) => voidRequired.
items{ id, title, target?, detail?, irreversible? }[]Required. Phrase title as the consequence.
onSend(ids: string[]) => voidRequired.
onDiscard(ids: string[]) => voidAdds a Discard action.
onEdit(id: string) => voidAdds Edit on each item.
offWarningstringWhat happens with the mode off, in your product's words.
emptyLabelstringShown when nothing is waiting.
titlestringDefaults to "Draft mode".
headingLevel2 | 3 | 4 | 5 | 6Defaults to 3.

Accessibility

  • A labelled <section>; the mode control is a real role="switch" with its state in words as well as position.
  • Each item is a checkbox with a full label, so the whole row is a hit target.
  • Sends and discards are announced through a polite live region, with the count.
  • "Can't be undone" is text, not only a colour.

When not to use

  • One high-risk action awaiting a decision is AI Approval Gate, which states the consequence and takes the decision on its own.
  • Many AI-proposed edits to review row by row is AI Triage Table.
  • Choosing how much the agent may do without asking is Autonomy Control. Draft mode is about what leaves, not about what the agent may attempt.

why it's built this way

Engineering Notes

Selection is the component's only state, and it is pruned against `items` on every change: an id that has left the list is dropped, because a stale id surviving in the selection is exactly how a batch action sends the wrong thing. There is no accept-all shortcut hiding behind a single control — 'Select all' is a visible, reversible selection step before the send, and the send button carries the count so the action names its own size. Irreversibility is counted from the chosen items rather than shown per row only, so the sentence under the button changes as the selection changes and the warning cannot be scrolled past. The mode switch is a real `role="switch"` with an explanatory sentence that rewrites itself for each state, instead of a settings label that leaves people guessing what 'off' does; turning it off does not hide the consequence, it states it in the waiting colour and offers the way back.

how it moves, and when it doesn't

Motion Notes

Nothing animates except the switch itself, which slides its thumb over `--pd-duration-fast` so the state change reads as a physical flip rather than a repaint. No item pulses, shimmers or slides in: this surface is entirely the user's turn, and motion here would imply the agent is still doing something. Under `prefers-reduced-motion` the switch transition is dropped and the state is carried by position, colour and the word On or Off.