Composer

Where the client writes to Okami: a liquid-glass pill with a textarea that grows as they type and an ink send button.

Playground

Composer
State

In the thread

Try it. Enter sends and Shift+Enter starts a new line; an IME composition is never cut short. While Okami's reply is on its way the send button waits and the TypingIndicator takes Okami's side of the thread — the composer adds no second indicator. Focus returns to the textarea when the reply lands.

Hi! How can I help?

Listening

While it dictates, the wave (VoiceWave) takes the text's place: a row of resting pills, as many as fit, that the voice raises smooth hills along where it lands. A loud moment makes a tall hill; each rises on --duration-micro and falls away on --duration-fast, so the row moves as the voice does. Under reduced motion the hills step in, still. Shown here without a microphone: the landing's ask as a real voice said it, over and over.

API reference

PropTypeDefaultDescription
valuestring—The draft. Controlled.
onValueChange(value: string) => void—Called as the client types.
onSend(value: string) => void—Called with the text to send on Enter or the send button — only when the draft isn't blank and nothing is busy or disabled. Sent straight from the microphone, it's the draft with what was said after it.
placeholderstring—Also the textarea's accessible name.
sendLabelstring—The send button's accessible name.
busybooleanfalseA reply is on its way: typing stays open, sending waits.
surface"glass" | "frost" | "solid""glass"Liquid glass on the atmosphere; a frosted pill like a Chip's on a glass panel, where glass inside glass goes grey; solid white with a mild border on a white page, where glass only refracts what scrolls under it.
disabledbooleanfalseNeither typing nor sending.
attach{ onFile, label, accept? }—A paperclip before the text: onFile takes the picked file, label names the button (“Attach a price list”), accept is what the picker offers. Left out, the composer is text only.
dictate{ transcribe, onError, labels }—A microphone before send. While it listens a live wave (VoiceWave) takes the text's place: the voice raises smooth hills along a row of resting pills, a loud moment a tall one, each rising and falling away in about a third of a second. It steps under reduced motion. × drops the recording, ■ puts the words in the field to check, and ↑ sends them as they are. transcribe turns the recording into words (transcribeRecording posts it to /api/voice); onError hears “denied” or “failed”; labels name the five states. Stops by itself after two minutes. Hidden where the browser can't record.
shown{ phase, levels, taken? }—A dictation shown without listening, for a staged screen (the landing's owner demo): phase is “recording” or “transcribing”, levels the loudness so far, 0 to 1, oldest first, one per 60ms; taken how many in all, when levels keeps only the newest. Needs dictate for its labels.
beambooleanfalseA beam riding the pill's edge in the studio's colours (the UI pair, --mix-studio and --mix-studio-2): an EdgeBeam (the full border), turning on the compositor; the border keeps its own colour: only light bleeding in from the edges near its head, travelling with it. Off under reduced motion. The owner's composer and the FloatingComposer wear it.
refRef<HTMLTextAreaElement>—The textarea — to return focus after a reply.
classNamestring—Layout only, on the form — padding, safe areas.

sendsOnEnter(event) is exported too: the key rule on its own, for any other text box that sends on Enter.

Guidelines

  • One composer per conversation, pinned to the bottom of the page and clear of the safe area. Free text always stays open — quick-reply Chips are shortcuts, never a replacement.
  • The composer is chrome, so it is the one place the true liquid glass (GlassPanel) is spent; the messages above it use the cheaper frost.
  • Never clear the draft until it has been handed to onSend, and hand focus back to the textarea once the reply lands.