v1.2.0
  1. ds
  2. Agent
  3. Composer

Composer

The message surface: model, permission, send and stop.

↵ send ⇧↵ newline
Show code
<div class="composer" id="cmp-demo" data-state="idle">
  <label class="sr-only" for="cmp-input">Message</label>
  <textarea class="composer-input" id="cmp-input" rows="2" placeholder="Ask axon to inspect, change, or run something">Trace the slow checkout requests and propose a fix.</textarea>
  <div class="composer-bar">
    <div class="composer-tools">
      <button class="chip" type="button" aria-haspopup="menu" aria-expanded="false"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg><span>Sonnet 4.5</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
      <button class="chip" type="button" aria-haspopup="menu" aria-expanded="false"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg><span>Workspace write</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
      <button class="btn icon sm ghost" type="button" aria-label="Attach a file"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg></button>
    </div>
    <span class="composer-hint"><kbd class="kbd">↵</kbd> send <kbd class="kbd">⇧↵</kbd> newline</span>
    <div class="composer-actions">
      <button class="btn sm primary composer-send" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>Send</button>
      <button class="btn sm composer-stop" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><rect width="12" height="12" x="6" y="6" rx="2"/></svg>Stop</button>
    </div>
  </div>
</div>

The composer is the one surface that stays on screen all session, so it carries the choices that change per turn: the model, the permission mode and the send action. The textarea grows to a limit, then scrolls. The chips are real buttons with aria-haspopup; the menus behind them are the existing menu.

This demo works. Type and press Enter to send, or use the button. While it runs, the send action becomes Stop and Escape cancels. Shift and Enter inserts a new line.

Running and queued

1 queuedEnter steers the running turn. Stop cancels it.
Show code
<div class="composer" data-state="running">
  <label class="sr-only" for="cmp-run">Steer the running turn</label>
  <textarea class="composer-input" id="cmp-run" rows="2">Also check whether the retry queue can drain out of band.</textarea>
  <div class="composer-bar">
    <div class="composer-tools">
      <button class="chip" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg><span>Sonnet 4.5</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
      <button class="chip" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg><span>Workspace write</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
    </div>
    <div class="composer-actions">
      <button class="btn sm primary composer-send" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>Send</button>
      <button class="btn sm composer-stop" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><rect width="12" height="12" x="6" y="6" rx="2"/></svg>Stop</button>
    </div>
  </div>
  <div class="composer-queue">
    <span class="status-dot live"></span><b>1 queued</b><span>Enter steers the running turn. Stop cancels it.</span>
    <button class="btn xs ghost" type="button">Clear</button>
  </div>
</div>

A running turn keeps the draft visible, so the next instruction can be written while the agent works. The queue row says what Enter will do and gives Stop a second, closer target. One queued turn is enough; more is a queue people cannot see.

Disabled and error

No model key for this provider. Settings has the fields.

Code
<div class="composer" data-state="error" aria-disabled="true">
  <label class="sr-only" for="cmp-err">Message</label>
  <textarea class="composer-input" id="cmp-err" rows="2" disabled>The model is not reachable. Your draft is kept.</textarea>
  <p class="composer-error"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span>No model key for this provider. Settings has the fields.</span></p>
  <div class="composer-bar">
    <div class="composer-tools">
      <button class="chip" type="button" disabled><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg><span>No model</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
      <button class="chip" type="button" disabled><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg><span>Workspace write</span><svg class="i chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
    </div>
    <div class="composer-actions">
      <button class="btn sm primary composer-send" type="button" disabled><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>Send</button>
    </div>
  </div>
</div>

The error stays on the surface, next to the send action, and the draft is never cleared. A disabled composer keeps its chips visible but disabled, so the reason is inspectable rather than hidden.

API

ClassOnWhat it does
.composerdivThe surface. data-state: idle, running, error.
.composer-inputtextareaGrows to 12.5rem, then scrolls. Show a label or use aria-label.
.composer-bardivThe toolbar under the text.
.composer-toolsdivModel, permission and attach controls.
.chip.composerModel and permission triggers. The ds chip with a softer radius and sans text.
.composer-actionsdivSend and stop, pinned right.
.composer-send / .composer-stopbuttonSwapped by data-state="running". Use real buttons.
.composer-hintspanThe Enter and Shift+Enter reminder. Hidden under 560px.
.composer-queuedivThe queued and steer line. .btn at the end clears it.
.composer-errorpThe failure line above the bar.
aria-disabled="true".composerDims the surface and disables the controls.
Do
  • Keep send disabled until there is text.
  • Swap one action for Stop instead of showing both at once.
  • Say what Enter does when a turn is already running.
Don't
  • Clear the draft on failure.
  • A permission chip that changes mode without saying so.
  • More than one filled button in the bar.

Accessibility

  • The textarea needs a label. The demo uses .sr-only rather than a placeholder alone.
  • Enter sends and Shift+Enter adds a line. Escape cancels a running turn. State this in the hint or in the help text.
  • Stop is a button, not a link, and it is reachable from the keyboard. It is not the only way to cancel: Escape works while the textarea has focus.
  • At 390px the hint is removed, the bar wraps and the chips truncate. Send and Stop keep their labels.