# Instant Message Composer URL: /docs/web/components/instant-message-composer Composer for SMS and WhatsApp with channel switching, recipient chips, attachments, and SMS character counting. **Demo:** ```tsx 'use client'; import { useCallback, useState } from 'react'; import { InstantMessageComposer, type InstantMessageAttachment, type InstantMessageChannel, type InstantMessageComposerSize, type InstantMessageComposerVariant } from '@docyrus/ui/components/instant-message-composer'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@docyrus/ui/primitives/ui/select'; import { Switch } from '@docyrus/ui/primitives/ui/switch'; import { cn } from '@docyrus/ui/primitives/lib/utils'; const SAMPLE_BODIES: Record
Size
Channel
); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-instant-message-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 { InstantMessageComposer, type InstantMessageChannel } from "@docyrus/ui/components/instant-message-composer"; {}} recipients={['+15551234567']} onRecipientsChange={(recipients) => {}} body="Hello!" onBodyChange={(body) => {}} onSend={() => {}} onAttach={() => {}} onDiscard={() => {}} /> ``` The composer is fully controlled. The send button is enabled once at least one valid phone number is entered and the body (or an attachment, on WhatsApp) is non-empty. Phone numbers are validated against a permissive E.164-style regex (6–18 digits, optional `+` prefix, common separators allowed); invalid chips are shown with a destructive style so users can correct them before sending. ### Channels The composer renders a small SMS / WhatsApp segmented switcher above the recipients row. Pass `availableChannels` to limit the choices, or omit `onChannelChange` to lock the composer to a single channel. ```tsx // Lock to WhatsApp only ``` ### SMS character counting When `channel="sms"`, the footer shows a live character counter and segment estimate (`160 chars · 1 SMS`). Concatenated SMS uses 153 characters per segment to account for UDH overhead. The body is also clamped to `maxLength` (default `1600` for SMS, unlimited for WhatsApp). Pass an explicit number to override, or `null` to remove the limit. ### WhatsApp formatting WhatsApp messages support inline formatting using their native syntax — `*bold*`, `_italic_`, `~strike~`, and `` ```mono``` ``. The composer hints this under the body when the WhatsApp channel is active. Attachments and the attach button are only shown for the WhatsApp channel. ## Variants | Variant | Description | |---------|-------------| | `default` | Default style with input border and shadow | | `outline` | Outline border, no shadow | | `minimal` | No border or shadow — for embedding in other surfaces (dialogs, sheets) | ## Sizes | Size | Description | |------|-------------| | `sm` | Small — compact text and shorter body min-height | | `default` | Default — comfortable text and 150px min body | | `lg` | Large — bigger text and taller body min-height | ## API Reference | Prop | Type | Default | Description | |------|------|---------|-------------| | `variant` | `"default"` \| `"outline"` \| `"minimal"` | `"default"` | Visual style | | `size` | `"sm"` \| `"default"` \| `"lg"` | `"default"` | Sizing scale | | `channel` | `InstantMessageChannel` | — | Active channel: `'sms'` or `'whatsapp'`. Required. | | `onChannelChange` | `(channel: InstantMessageChannel) => void` | — | Called when the user switches channels. Omit to lock the channel. | | `availableChannels` | `ReadonlyArray` | `['sms', 'whatsapp']` | Channels offered in the segmented switcher | | `recipients` | `string[]` | — | Recipient phone numbers (chip list). Required. | | `onRecipientsChange` | `(recipients: string[]) => void` | — | Called when chips are added/removed/pasted | | `body` | `string` | — | Plain-text message body. Required. | | `onBodyChange` | `(body: string) => void` | — | Called when the body changes | | `onSend` | `() => void` | — | Fired when the user clicks Send | | `onAttach` | `() => void` | — | Fired when the user clicks the paperclip (WhatsApp only) | | `onDiscard` | `() => void` | — | Fired when the user clicks Cancel | | `sending` | `boolean` | `false` | Shows a spinner on the send button and disables interactions | | `disabled` | `boolean` | `false` | Disables all inputs and actions | | `attachments` | `InstantMessageAttachment[]` | — | List of attached files (WhatsApp only) | | `onRemoveAttachment` | `(index: number) => void` | — | Called when an attachment chip is removed | | `maxLength` | `number \| null` | `1600` for SMS, no limit for WhatsApp | Hard cap on body length. Pass `null` to remove the limit. | | `helper` | `ReactNode` | — | Optional helper rendered between the recipients row and the body | | `className` | `string` | — | Extra classes for the root | ## Type Exports | Type | Description | |------|-------------| | `InstantMessageComposerProps` | Props for the composer | | `InstantMessageComposerVariant` | `'default' \| 'outline' \| 'minimal'` | | `InstantMessageComposerSize` | `'sm' \| 'default' \| 'lg'` | | `InstantMessageChannel` | `'sms' \| 'whatsapp'` | | `InstantMessageAttachment` | `{ name: string; size: number }` | ## Recipe: Sending to a contact selection Pull recipients out of a row selection (data grid, list, etc.) and feed them into the composer as the initial chip list. Treat the controlled `recipients` prop as the source of truth so users can still tweak the chips in the dialog before pressing Send. ```tsx const selectedContacts = useSelectedContacts(); const phones = useMemo( () => Array.from(new Set( selectedContacts.map(c => c.mobile?.trim()).filter((p): p is string => Boolean(p)) )), [selectedContacts] ); const [recipients, setRecipients] = useState(phones); const [body, setBody] = useState(''); const [channel, setChannel] = useState('sms'); return ( sendMessage({ channel, recipients, body })} /> ); ```