Skip to content

Fields

Composer

The message input of a chat: Enter sends, Shift+Enter adds a line, stop interrupts a reply.

When to use it: Use it under Messages for any chat or assistant surface. For a one-off multi-line field use TextArea.

On this page

Example

Loading example…
Composer.tsxtsx
import { useState } from "react";
import { Composer, ComposerInput, ComposerSend, ComposerStop } from "@comp0/react";

export function Example() {
  const [sent, setSent] = useState<string[]>([]);
  const [generating, setGenerating] = useState(false);

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Composer
        generating={generating}
        onSend={(text) => {
          setSent((current) => [...current, text]);
          setGenerating(true);
        }}
        onStop={() => setGenerating(false)}
        className="flex items-end gap-2"
      >
        <ComposerInput
          aria-label="Message"
          placeholder="Message the assistant"
          className="min-h-11 min-w-0 flex-1 resize-none rounded border border-zinc-950/10 bg-white px-3 py-2.5 text-base text-zinc-950 outline-teal-600 focus-visible:outline-2 sm:py-2 sm:text-sm dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-50 dark:outline-teal-400"
        />
        <ComposerSend className="rounded bg-teal-600 px-3 py-2.5 text-base text-white outline-teal-600 focus-visible:outline-2 disabled:opacity-50 sm:py-2 sm:text-sm dark:bg-teal-500 dark:text-zinc-950 dark:outline-teal-400">
          Send
        </ComposerSend>
        <ComposerStop className="rounded border border-zinc-950/10 bg-white px-3 py-2 text-base text-zinc-900 outline-teal-600 focus-visible:outline-2 disabled:opacity-50 sm:text-sm dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-100 dark:outline-teal-400">
          Stop
        </ComposerStop>
      </Composer>
      <p className="text-sm text-zinc-600 dark:text-zinc-400">
        Enter sends, Shift+Enter adds a line. Sent: {sent.length}
      </p>
    </div>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. Composer

    Native form that owns the draft. Owns a DOM element.

  2. ComposerInput

    The textarea; Enter sends unless Shift or Alt is held or an IME composition is active. Owns a DOM element.

  3. ComposerSend

    Submit button, disabled while the draft is empty or a reply is generating. Owns a DOM element.

  4. ComposerStopOptional

    Stop button; renders only while generating. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap ComposerInput in Composer and name the input with aria-label or a label.

  2. 2

    Add the supporting parts

    Add ComposerSend and, for model replies, ComposerStop.

  3. 3

    Make the behavior clear

    Handle onSend with the draft and set generating while the reply streams; stop shows up and sending pauses.

    Exampletsx
    <Composer onSend={send} onStop={stop} generating={generating}>
      <ComposerInput aria-label="Message" />
      <ComposerSend>Send</ComposerSend>
      <ComposerStop>Stop</ComposerStop>
    </Composer>;

Keyboard

↵
Sends the draft.
⇧↵
Inserts a new line.

Forms and accessibility

The input submits as the field named message, so the form also works natively.

Accessibility checklist

  • Give ComposerInput an accessible name with aria-label or a Label.
  • Enter that confirms an IME composition (Japanese, Chinese, Korean) never sends.
  • Focus returns to the input after sending or stopping, so the next message can be typed at once.
  • Announce when generation starts or ends with a Status or the Messages log, not by moving focus.

API reference

Importtsx
import { Composer, ComposerInput, ComposerSend, ComposerStop } from "@comp0/react";

Composer

DOM element

Native form that owns the draft.

PropTypeDescription
valuestringControlled draft.
defaultValuestringInitial draft.
onChange(value: string) => voidReceives the next draft text.
onSend(value: string) => voidCalled with a non-empty draft; the draft is cleared afterwards. Native onSubmit still runs first and can veto.
onStop() => voidCalled when the user stops the reply.
generatingbooleanA reply is being generated: sending pauses and ComposerStop appears.
disabledbooleanDisables the input and sending.
asElementTypeRenders another element in place of the form.

ComposerInput

DOM element

The textarea; Enter sends unless Shift or Alt is held or an IME composition is active.

ComposerSend

DOM element

Submit button, disabled while the draft is empty or a reply is generating.

ComposerStop

OptionalDOM element

Stop button; renders only while generating.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

Composer

Style hookMeaning
[data-generating]A reply is being generated.

Keep exploring