# Email Composer
URL: /docs/web/components/email-composer
Rich text email composer with sender-account picker, formatting toolbar, recipients, attachments, and i18n support.
**Demo:**
```tsx
'use client';
import {
useCallback, useMemo, useState
} from 'react';
import { CalendarDays, FileText } from 'lucide-react';
import {
EmailComposer,
type EmailAttachment,
type EmailComposerMentionUser,
type EmailComposerSize,
type EmailComposerSlashCommand,
type EmailComposerSlashCommandContext,
type EmailComposerVariant
} from '@docyrus/ui/components/email-composer';
import { Button } from '@docyrus/ui/primitives/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle
} from '@docyrus/ui/primitives/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@docyrus/ui/primitives/ui/select';
import { Switch } from '@docyrus/ui/primitives/ui/switch';
import { Textarea } from '@docyrus/ui/primitives/ui/textarea';
import { cn } from '@docyrus/ui/primitives/lib/utils';
const MENTION_USERS: EmailComposerMentionUser[] = [
{
id: '1', firstname: 'Alex', lastname: 'Johnson', email: 'alex@docyrus.com'
},
{
id: '2', firstname: 'Maria', lastname: 'Garcia', email: 'maria@docyrus.com'
},
{
id: '3', firstname: 'James', lastname: 'Smith', email: 'james@docyrus.com'
},
{
id: '4', firstname: 'Yuki', lastname: 'Tanaka', email: 'yuki@docyrus.com'
},
{
id: '5', firstname: 'Fatma', lastname: 'Yilmaz', email: 'fatma@docyrus.com'
}
];
/**
* Developer-drawn dialog for the `/template` slash command — the demo owns the
* whole dialog (shell + Cancel / Send buttons) and only calls
* `context.insert(html)` / `context.cancel()`.
*/
function TemplateSlashDialog({ context }: { context: EmailComposerSlashCommandContext }) {
const [text, setText] = useState('Hi,\n\nThanks for reaching out — here is the summary we discussed.');
const handleInsert = useCallback(() => {
const html = text
.split('\n')
.map(line => (line.trim() ? `
${line}
` : '
'))
.join('');
context.insert(html);
}, [context, text]);
return (
);
}
export function EmailComposerDemo() {
const [variant, setVariant] = useState
Size
);
}
```
## Installation
```bash
pnpm dlx @docyrus/cli add @docyrus/ui-email-composer
```
**Dependencies:**
- [lucide-react](https://www.npmjs.com/package/lucide-react)
- [class-variance-authority](https://www.npmjs.com/package/class-variance-authority)
## Usage
```tsx
import {
EmailComposer
} from "@docyrus/ui/components/email-composer";
);
}
```
Notes:
- The insertion position is saved when the command is picked, so the dialog can stay open as long as needed — `insert` lands the HTML exactly where the `/` was typed (falling back to the end of the body if that position no longer exists).
- Render whatever dialog implementation you like (the `Dialog` primitive, a custom modal, a drawer) — the returned node is rendered inside the composer, and portal-based dialogs work as expected.
- Inserted HTML is delivered to recipients as-is: use inline styles for any formatting, the app's stylesheet does not exist in email clients.
- Omit `slashCommands` (or pass an empty array) to disable the feature entirely.
## Variants
| Variant | Description |
|---------|-------------|
| `default` | Default style |
| `outline` | Outline — `border-border` |
| `minimal` | Minimal — `border-transparent shadow-none` |
## Sizes
| Size | Description |
|------|-------------|
| `sm` | Small — `text-xs` |
| `default` | Default — `text-sm` |
| `lg` | Large — `text-base` |
## API Reference
| Prop | Type | Default |
|------|------|---------|
| `variant` | `"default"` \| `"outline"` \| `"minimal"` | `"default"` |
| `size` | `"sm"` \| `"default"` \| `"lg"` | `"default"` |
| `accounts` | `EmailComposerAccount[]` | — | Sender accounts surfaced in the From dropdown. Hides the row when omitted. |
| `selectedAccountId` | `string \| null` | — | Currently selected account id. |
| `onSelectedAccountChange` | `(accountId: string) => void` | — | Called when the user picks a different account from the dropdown. |
| `to` | `string[]` | — |
| `onToChange` | `(to: string[]) => void` | — |
| `cc` | `string[]` | — |
| `onCcChange` | `(cc: string[]) => void` | — |
| `bcc` | `string[]` | — |
| `onBccChange` | `(bcc: string[]) => void` | — |
| `subject` | `string` | — |
| `onSubjectChange` | `(subject: string) => void` | — |
| `body` | `string` | — | HTML body content (rich text via contentEditable) |
| `onBodyChange` | `(body: string) => void` | — | Called with HTML string on body change |
| `mentionUsers` | `EmailComposerMentionUser[]` | — | Users offered by the body editor's `@` mention autocomplete. Omit to disable mentions. |
| `slashCommands` | `EmailComposerSlashCommand[]` | — | Developer-defined commands offered by the body editor's `/` autocomplete. Omit to disable slash commands. |
| `onSend` | `() => void` | — |
| `onAttach` | `() => void` | — |
| `onDiscard` | `() => void` | — |
| `sending` | `boolean` | — |
| `disabled` | `boolean` | — |
| `attachments` | `EmailAttachment[]` | — |
| `onRemoveAttachment` | `(index: number) => void` | — |
| `showToolbar` | `boolean` | `true` |
| `signature` | `string` | — | HTML content of the email signature |
| `onSignatureChange` | `(signature: string) => void` | — | Called when signature content changes. When provided, the signature area becomes editable |
| `signatureVisible` | `boolean` | `true` | Whether the signature is visible (when signature is provided) |
| `onSignatureVisibleChange` | `(visible: boolean) => void` | — | Called when the user toggles signature visibility |
| `className` | `string` | — |
## Type Exports
| Type | Description |
|------|-------------|
| `EmailComposerAccount` | `{ id; name; senderEmail; senderName?; kind?: 'tenant' \| 'user'; provider?: string \| null; isUserAccessible? }` — minimum shape consumed by the From dropdown. `provider` drives the brand icon (Google / Microsoft / AWS, else a generic mail icon). The Docyrus-backed account DTO (`DocyrusEmailAccount`) extends this. |
| `EmailAttachment` | `{ name; size }` — local attachment metadata for display. |
| `EmailComposerMentionUser` | `{ id; firstname?; lastname?; email?; photo? }` — user entry consumed by the `@` mention autocomplete. `email` powers the chip's `mailto:` link, `photo` the list avatar. |
| `EmailComposerSlashCommand` | `{ id; label; description?; icon?; keywords?; getContent?; renderDialog? }` — a `/` command definition. `getContent` returns HTML to insert (async supported); `renderDialog` renders a fully developer-owned dialog instead. |
| `EmailComposerSlashCommandContext` | `{ query; insert(html); cancel() }` — bridge API handed to `renderDialog`. `insert` injects at the trigger position and closes; `cancel` closes without inserting. |
| `EmailComposerVariant` | `'default' \| 'outline' \| 'minimal'`. |
| `EmailComposerSize` | `'sm' \| 'default' \| 'lg'`. |