Theme studio

Design a theme, preview it live, then export it. Saved in this browser.

Quick picks #10b981
Generated scale
50100200300400500600700800900
Some text is below AA

Composer

  • Stable
  • WCAG 2.2 evidence
  • RTL

The message box under a conversation: a growing textarea with attach, emoji and voice buttons, a Send button, a "replying to" bar, attachments and a voice recording row. Built on React Aria TextField + TextArea and Buttons. The textarea is named by label, aria-label or aria-labelledby (never the placeholder) and described by the helper text, the reply bar, the error and the keyboard hint (WCAG 1.3.1, 3.3.2, 4.1.2). Enter sends and Shift+Enter adds a new line; IME composition never sends; Escape cancels a reply (2.1.1). Every button has an accessible name and a 24 px or larger target (2.5.8, 4.1.2); Send is disabled while there is nothing to send and focus returns to the textarea after sending. Errors show an icon and text and are announced (1.4.1, 3.3.1, 4.1.3); recording is announced. Paste, autofill (autoComplete) and spell checking are never blocked (3.3.7, 3.3.8). The field wraps and grows instead of scrolling sideways (1.4.10, 1.4.12). Controlled (value + onChange) or uncontrolled (defaultValue; cleared after sending). Consumer duties: the label and every translated button name; an error message that says what to do; sendOnEnter={false} where Enter must add lines; announcing sent and received messages through the conversation's role="log".

import { Composer } from "@nexera-ui/react";
Loading example…
5
examples
51
props
8
live controls
1
platform
14
WCAG criteria
4
blocks use it

Try every prop. Copy the code.

Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.

Loading…
placeholder
description
sendOnEnter
isRecording
isDisabled
isReadOnly
isRequired
isInvalid
Generated code
import { Composer } from "@nexera-ui/react";

<Composer />

Examples 4

The same examples as Storybook, rendered live. Open Code to copy one.

Interactive

A working composer: reply, attach a file, record a voice note and send.

Loading example…

Label and error

A visible label, helper text and a send error (icon and text, announced when it appears).

Loading example…

Long message

A long message wraps and the field grows up to about six lines, then scrolls inside the field.

Loading example…

Right to left

Right-to-left: attach on the right, Send on the left; the reply bar's accent mirrors.

Loading example…

Props 51

Press "Try it" on a card to load that prop into the playground.

51 props shown

placeholderNexera
string

Example text while the field is empty. Never the only name: give the composer a label or aria-label too.

Default –
descriptionNexera
ReactNode

Helper text under the row, linked with aria-describedby (WCAG 3.3.2).

Default –
errorMessageNexera
ReactNode | ((validation: ValidationResult) => ReactNode)

Error text under the row (for example "Message not sent. Check your connection."), shown with an icon while isInvalid is true, linked with aria-describedby and announced politely when it appears (WCAG 3.3.1, 4.1.3).

Default –
onSendNexera
(message: string) => void

Called with the message when the user sends it: the Send button, or Enter when sendOnEnter. Not called for an empty (whitespace-only) message unless attachments are shown. While isRecording, Send calls it with the typed text (usually empty): send the recording then. Uncontrolled composers clear themselves after sending.

Default –
sendLabelNexera
string

Accessible name of the Send button. Translate it.

Default Send
sendOnEnterNexera
boolean

Enter sends and Shift+Enter inserts a line break (the chat convention). While an input method editor is composing (Chinese, Japanese, Korean), Enter confirms the composition and never sends. Set false where Enter should always add a line break (for example long-form replies, or on touch keyboards), so only the Send button sends.

Default true
keyboardHintNexera
string

Visually hidden instruction linked to the textarea with aria-describedby while sendOnEnter is true, so screen reader users know how Enter behaves before they type (WCAG 3.3.2). Translate it; pass "" to leave it out.

Default Enter sends the message. Shift+Enter adds a new line.
onAttachNexera
() => void

Shows the attach button and is called when it is pressed, for example to open a file picker.

Default –
attachLabelNexera
string

Accessible name of the attach button. Translate it.

Default Attach file
onEmojiNexera
() => void

Shows the emoji button in the field and is called when it is pressed.

Default –
emojiLabelNexera
string

Accessible name of the emoji button. Translate it.

Default Add emoji
onRecordNexera
() => void

Shows the voice button in the field and is called when it is pressed: start recording and set isRecording.

Default –
recordLabelNexera
string

Accessible name of the voice button. Translate it.

Default Record voice message
attachmentsNexera
ReactNode

Attached files shown above the row, for example removable Badges. While present, Send is enabled even without text.

Default –
replyToNexera
ReactNode

The message being replied to: shows the reply bar above the row. It is linked to the textarea with aria-describedby, so it is read with the field.

Default –
replyAuthorNexera
ReactNode

Author of the message being replied to, shown above replyTo.

Default –
replyLabelNexera
string

Visually hidden text that starts the reply bar ("Replying to Sara Malik:..."). Translate it.

Default Replying to
onCancelReplyNexera
() => void

Shows the reply bar's close button and is called when it is pressed or when Escape is pressed in the field. Focus returns to the textarea.

Default –
cancelReplyLabelNexera
string

Accessible name of the reply bar's close button. Translate it.

Default Cancel reply
isRecordingNexera
boolean

A voice message is being recorded: the row shows a red dot, the elapsed time, a waveform, Cancel and Send instead of the field. Entering the state is announced (recordingLabel) and focus moves to Cancel when it was in the composer; leaving it returns focus to the textarea.

Default false
recordingTimeNexera
string

Elapsed recording time, for example "0:07". Read as a timer, not announced every second.

Default –
recordingLabelNexera
string

Announced when recording starts and read before the time. Translate it.

Default Recording
onCancelRecordingNexera
() => void

Called by the Cancel button of the recording row: discard the recording and clear isRecording.

Default –
cancelRecordingLabelNexera
string

Label of the Cancel button of the recording row. Translate it.

Default Cancel
waveformNexera
ReactNode

Live waveform of the recording. Decorative. Defaults to static bars.

Default –
textAreaRefNexera
Ref<HTMLTextAreaElement>

Ref to the native <textarea> (the root ref points to the composer).

Default –
classNameNexera
string

Extra classes for the root, merged last so they win over the defaults.

Default –
styleNexera
CSSProperties

Inline style of the root.

Default –
labelNexera
FieldLabelContent

Visible label, linked to the control by React Aria (WCAG 1.3.1, 3.3.2). Short noun, for example "Work email". Wraps instead of truncating. A placeholder is never a substitute for it. Not set: the field has no visible label. Not set: the field has no visible label of its own.

Default –
aria-labelNexera
string

Accessible name when it must differ from the visible label. Prefer the visible label (WCAG 2.5.3). Accessible name. Required when there is no visible label; translate it. Accessible name. Optional when aria-labelledby is set.

Default –
aria-labelledbyNexera
string

Id(s) of element(s) that name the field; wins over the visible label. Id(s) of element(s) that name the field; wins over aria-label. Id(s) of visible element(s) that name the field.

Default –
validationBehaviorReact Aria
"native" | "aria"

Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA.

Default 'native'
enterKeyHintReact Aria
"search" | "enter" | "done" | "go" | "next" | "previous" | "send"

An enumerated attribute that defines what action label or icon to preset for the enter key on virtual keyboards. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint).

Default –
isDisabledReact Aria
boolean

Whether the input is disabled.

Default false
isReadOnlyReact Aria
boolean

Whether the input can be selected but not changed by the user.

Default –
isRequiredReact Aria
boolean

Whether user input is required on the input before form submission.

Default –
isInvalidReact Aria
boolean

Whether the value is invalid.

Default –
validateReact Aria
(value: string) => true | ValidationError | null

A function that returns an error message if a given value is invalid. Validation errors are displayed to the user when the form is submitted if validationBehavior="native". For realtime validation, use the isInvalid prop instead.

Default –
autoFocusReact Aria
boolean

Whether the element should receive focus on render.

Default –
valueReact Aria
string

The current value (controlled).

Default –
defaultValueReact Aria
string

The default value (uncontrolled).

Default –
onChangeReact Aria
(value: string) => void

Handler that is called when the value changes.

Default –
aria-describedbyReact Aria
string

Identifies the element (or elements) that describes the object.

Default –
idReact Aria
string

The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).

Default –
autoCompleteReact Aria
string

Describes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete).

Default –
maxLengthReact Aria
number

The maximum number of characters supported by the input. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefmaxlength).

Default –
minLengthReact Aria
number

The minimum number of characters required by the input. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefminlength).

Default –
autoCorrectReact Aria
string

An attribute that takes as its value a space-separated string that describes what, if any, type of autocomplete functionality the input should provide. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#autocomplete).

Default –
spellCheckReact Aria
string

An enumerated attribute that defines whether the element may be checked for spelling errors. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck).

Default –
nameReact Aria
string

The name of the input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname).

Default –
formReact Aria
string

The <form> element to associate the input with. The value of this attribute must be the id of a <form> in the same document. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#form).

Default –

* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.

Accessible by default.

Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.

WCAG 2.2 evidence

10 direct · 4 supporting
  • 1.3.1Info and RelationshipsLevel A · tested directly
  • 1.4.1Use of ColorLevel A · tested directly
  • 1.4.10ReflowLevel AA · supporting test
  • 1.4.11Non-text ContrastLevel AA · supporting test
  • 1.4.12Text SpacingLevel AA · supporting test
  • 2.1.1KeyboardLevel A · tested directly
  • 2.4.3Focus OrderLevel A · tested directly
  • 2.4.7Focus VisibleLevel AA · supporting test
  • 3.3.1Error IdentificationLevel A · tested directly
  • 3.3.2Labels or InstructionsLevel A · tested directly
  • 3.3.7Redundant EntryLevel A · tested directly
  • 3.3.8Accessible Authentication (Minimum)Level AA · tested directly
  • 4.1.2Name, Role, ValueLevel A · tested directly
  • 4.1.3Status MessagesLevel AA · tested directly

Fits any width.

Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.

0 px · drag the corner

Styling hooks

Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.

  • data-disabled
  • data-hovered
  • data-pressed
Usage
<Composer className="data-disabled:opacity-90 shadow-sm" />

Used in blocks

Related components