affectUI › Components › Chat

Chat Composer

Prompt composer with model picker, voice input, app connections and approval request.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/chat-composer.json · Import from @/components/ui/chat-composer

When to use

  • The main input of an AI assistant or agent that can act on connected tools
  • Flows where the user has to grant extra permissions before a run

When not to use

  • Plain chat between people – a simple text field with a send button is enough
  • Very narrow spaces below ~280px, where the toolbar labels get too small

Usage

import { ChatComposer } from "@/components/ui/chat-composer";

export function Assistant() {
  return <ChatComposer onSend={(message) => console.log(message)} />;
}

Props

NameTypeDefaultDescription
placeholderstring"Start by typing..."Placeholder of the message field
modelsChatModel[]defaultModelsEntries of the model picker (Auto, Claude, ChatGPT); the first is selected
loadMsnumber1300Duration of the connect and approval loading states
dictationstring"Draft a short update…"Demo transcript that appears word by word in gray while recording – replace with real speech-to-text
onSend(message: string, details: { model: string }) => voidCalled on send or Enter
onModelChange(model: ChatModel) => voidCalled when a model is picked
onAttach() => voidCalled when the plus button is clicked – open your own picker here
onConnectApps() => voidCalled when “Connect apps” is clicked
onRequestApproval() => voidCalled when “Request approval” is clicked
onSelectProject() => voidCalled when “Select a project” is clicked

Keyboard

  • Enter: Send the message
  • Shift + Enter: New line
  • ↑ / ↓: Open the model picker and move through it
  • Home / End: Jump to the first or last model
  • Enter / Space: Pick the highlighted model (in the picker)
  • Esc: Close the model picker or discard a recording

Accessibility

  • Loading and result states are announced through a polite live region
  • Busy buttons expose aria-busy; the hidden tray row is inert and aria-hidden
  • Clicking anywhere on the white card focuses the message field
  • The model picker is a listbox with aria-activedescendant, so screen readers follow the highlight

Responsive

  • Fills its container up to 680px wide; all sizes follow the width via container query units and stop growing at a compact maximum
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Guides