Locara
← All components Composites

ChatComposer

The full Cursor/Claude-style composer: model picker + mode tabs + attachments + slash + voice + ⌘↵.

Inspired by Cursor & Claude.ai composers; Anthropic Console.

Preview

What it looks like.

Try the model picker, mode switcher, attachments, slash menu (/) and ⌘↵ to submit.

Live — the real component, scoped to this frame.

Usage

Drop it into your app.

import { ChatComposer } from '@locara/components'

const [text, setText] = useState('')
const [model, setModel] = useState('qwen2.5-7b')

<ChatComposer
  value={text}
  onChange={setText}
  onSubmit={() => llm.chat({ model, messages: [...history, { role: 'user', content: text }] })}
  models={[
    { id: 'qwen2.5-7b', label: 'Qwen 2.5 7B', hint: '4.9 GB · Q4_K_M', badges: ['fast'] },
    { id: 'llama-3-70b', label: 'Llama 3 70B', hint: '42.8 GB', badges: ['heavy'], status: 'needs-download', sizeLabel: '42.8 GB' },
  ]}
  modelId={model}
  onModelChange={setModel}
  modes={[{ id: 'ask', label: 'Ask' }, { id: 'edit', label: 'Edit' }, { id: 'agent', label: 'Agent' }]}
  modeId="edit"
  onModeChange={() => {}}
  attachments={[{ id: '1', name: 'design-system.pdf', kind: 'pdf', sizeLabel: '2.1 MB' }]}
  onAttachClick={() => filePicker.open()}
  onVoiceClick={() => voice.start()}
  slashCommands={[
    { id: 'rewrite', label: 'Rewrite', description: 'Improve grammar and clarity' },
    { id: 'translate', label: 'Translate', description: 'To another language' },
  ]}
/>

Or copy the source into your repo: locara add chat-composer

Design notes

Why it works this way.

ChatComposer is intentionally batteries-included. Every sub-feature is optional — pass only `value/onChange/onSubmit` for a plain composer; layer on the model picker, mode tabs, attachments rail, slash menu, voice button as your app needs them. The ⌘↵ shortcut is baked in. Slash-command detection re-uses the underlying SlashMenu component so it shares a single keyboard model with anything else that uses slash menus.