Composer

The message field of a conversation, which grows with its text.

StableSince 1.0.0
On this page

Import

import { Composer } from '@envsemble/envui';

Usage

A Composer is a native textarea with no visible label, so name it with aria-label or aria-labelledby. Enter or the send button passes the text to onSend; blank text never sends, and neither does a disabled, running or pending composer.

  • Leave it uncontrolled, with defaultValue, and it clears after each send and when its form resets.
  • Or control it with value and onValueChange, and clear it yourself.
AskAnything.tsx
import { useEffect, useRef, useState } from 'react';
import { Composer, Column, Text } from '@envsemble/envui';

export default function AskAnything() {
  const [draft, setDraft] = useState('');
  const [sent, setSent] = useState('');
  const [running, setRunning] = useState(false);
  const timer = useRef<number>(undefined);
  useEffect(() => () => window.clearTimeout(timer.current), []);

  const send = (text: string) => {
    setSent(text);
    setDraft('');
    setRunning(true);
    // Stands in for a reply that streams for a few seconds.
    timer.current = window.setTimeout(() => setRunning(false), 4000);
  };
  const stop = () => {
    window.clearTimeout(timer.current);
    setRunning(false);
  };

  return (
    // As wide as a conversation column.
    <Column gap={3} style={{ inlineSize: 'min(100%, 32rem)' }}>
      <Composer
        aria-label="Message"
        placeholder="Ask anything"
        value={draft}
        onValueChange={setDraft}
        onSend={send}
        running={running}
        onStop={stop}
      />
      <Text variant="caption" emphasis="muted" role="status">
        {running ? 'Replying…' : sent ? `Sent: ${sent}` : ''}
      </Text>
    </Column>
  );
}

Pending and running

pending marks a send in progress: the send button shows a spinner and sending is unavailable, while the field stays editable and keeps focus, so use it rather than disabled. running marks a reply on its way: the send button becomes a stop button, which calls onStop and stays available when the field is disabled, and Enter doesn't send.

<Composer aria-label="Reply to Priya Shah" pending={sending} onSend={send} />
<Composer aria-label="Message" running={replying} onSend={send} onStop={stop} />

Sent by its form

Without onSend, Enter and the send button submit the composer's form, which sends the text under its name.

<form onSubmit={submit}>
  <Composer name="note" aria-label="Note" placeholder="Add a note to the plan" />
</form>

Rows, start and description

The field shows minRows lines while empty, 1 by default, and grows to maxRows, 8 by default, before it scrolls. start holds content at the start of the action row, such as an attach button or the name of a model, and description supporting text below the composer, which describes the field. sendLabel and stopLabel name the button.

<Composer
  aria-label="Comment"
  minRows={2}
  maxRows={6}
  start={<IconButton variant="text" icon={<IconAttachFile />} aria-label="Attach files" />}
  description="Files up to 10 MB."
  onSend={addComment}
/>

Composition

Place the composer below the MessageList it adds to.

Customize

The composer, env-composer, is on the raised popup surface, env-composer-surface, which holds the input, env-composer-input, the start content, env-composer-start, and the send button, env-composer-action; the footer, env-composer-footer, sits below it.

Accessibility

You provide

  • An aria-label that names the field.
  • An announcement of the reply, or of a message that couldn't be sent.
  • sendLabel and stopLabel in the application's language.

envUI provides

  • A native textarea described by the footer.
  • A send button named Send or Stop, which stays focusable as it changes.
  • Focus back in the field after a send.
  • Enter that leaves input method composition alone.

Props

Composer

The message field of a conversation: a multiline field on a raised surface that grows with its text up to maxRows, with a send button at the end that becomes a stop button while running. Enter submits, Shift+Enter starts a new line and an input method's composition never submits. Without onSend, Enter and the send button submit the field's form.

PropTypeDescription
value
string
The message text of a controlled composer.
defaultValueDefault ''
string
The starting text of an uncontrolled composer; it clears after each send through onSend and when its form resets.
onValueChange
(value: string) => void
Receives the text as it changes, and an empty string when an uncontrolled composer clears.
onSend
(value: string) => void
Enter or the send button submits the text as typed, and an uncontrolled composer clears. Blank text never submits, and neither does a disabled, running or pending composer. Without onSend, they submit the field's form instead and the text stays until the form resets. Without onSend or a form there is nothing to send: the button is unavailable and Enter starts a new line.
pendingDefault false
boolean
A send is in progress, such as a request the application makes: the send button shows a spinner and sending is unavailable, while the field keeps its text, stays editable and keeps focus. The pending action of an enclosing form counts too; set pending for a form the field joins through its form attribute, or for a request of the application's own.
runningDefault false
boolean
A reply is on its way: the send button becomes a stop button and Enter does not submit.
onStop
() => void
Called by the stop button while running; it stays available when the field is disabled.
sendLabelDefault 'Send', or EnvUIProvider's messages.composer.send
string
Accessible name of the send button.
stopLabelDefault 'Stop', or EnvUIProvider's messages.composer.stop
string
Accessible name of the stop button.
minRowsDefault 1
number
Lines the field shows while empty; 1 by default.
maxRowsDefault 8
number
Lines the field grows to before it scrolls; 8 by default.
start
ReactNode
Content at the start of the action row, such as an attach button or the name of a model.
description
ReactNode
Supporting text below the composer, such as a note on attachments; it describes the field.
aria-label
string
Required when aria-labelledby is not set. Names the field, which has no visible label, such as "Message"; this or aria-labelledby.
aria-labelledby
string
Required when aria-label is not set. The id of visible text that names the field; this or aria-label.

Also accepts the native props of <textarea>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-composer
The message composer.
env-composer-action
The send or stop button.
env-composer-footer
The row of actions under the input.
env-composer-input
The text input.
env-composer-start
The actions at the start of the footer.
env-composer-surface
The surface around the input and the footer.
data-disabled
Present
data-running
Present
data-start
Present

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.composer.send
"Send" The send button's name.
messages.composer.stop
"Stop" The stop button's name while a reply is generated.

Changelog

1.0.0

footer is description, as on a field, since it describes the field. onSubmit is onSend, which pairs with sendLabel and leaves the native submit event's name alone. The send and stop labels come from EnvUIProvider's messages, unless sendLabel and stopLabel set them.