Skip to main content
WireKit
Copy for LLM

Assistant Message

An AI turn. Message is the human chat bubble — this is its assistant-side counterpart: roles, a streaming body, a model chip, a reasoning disclosure, and — the part almost every AI chat UI gets wrong — announcements a screen-reader user can actually follow.

Drop it inside a Conversation and stream into it.

Basic Usage

An assistant turn
Assistant atlas-2
The export API allows 60 requests per minute per token. A 429 always includes a `Retry-After` header, in seconds.

Roles

assistant and user are the two sides of the turn; system is a configuration note — centered, muted, and no avatar, because a system turn is not a person.

The three roles
You
Summarize the rate limits.
Assistant atlas-2
Sixty requests a minute, per token.
System
Switched to the long-context model.

States

When a turn is a state — an error answer, a caution, a confirmation — intent tints the bubble. danger, warning, success and info mix the state color into the surface and border at low alpha; the body text stays regular, so the tint reads as a marker, never as low-contrast colored text.

State-tinted turns
Assistant atlas-2
I could not reach the export API — it returned a 503. Nothing was changed.
Assistant atlas-2
This will delete 1,204 rows and cannot be undone. Confirm to continue.
Assistant atlas-2
Done — all 412 rows imported cleanly.

Streaming and announcements

This is the important part.

The obvious approach — putting aria-live on the streaming body — makes a screen reader re-read the entire growing answer on every token. It is unusable, and it is what most AI chat interfaces ship.

Assistant Message keeps the body silent (aria-live="off", aria-busy while streaming) and mirrors complete sentences into a separate, always- present live region. The reader hears the answer as prose, one finished sentence at a time — never a stuttering re-read.

announce Behavior
sentence (default) Flush each finished sentence as it lands
all Flush everything once, when streaming stops (call flush())
off Never announce — you narrate it yourself
{{-- 1. wire:stream appends tokens into the body; the announcer stays readable --}}
<x-wirekit::assistant-message :streaming="$streaming" model="atlas-2">
    <span wire:stream="answer">{{ $answer }}</span>
</x-wirekit::assistant-message>
// 2. Flip $streaming around the streamed call
public bool $streaming = false;

public function ask(): void
{
    $this->streaming = true;
    $this->stream(to: 'answer', content: $token, replace: false);
    $this->streaming = false;
}

For announce="all", call flush() on the Alpine scope when the stream ends:

{{-- 3. Announce the whole answer once, instead of sentence by sentence --}}
<x-wirekit::assistant-message announce="all" x-on:stream-finished.window="flush()">
    <span wire:stream="answer">{{ $answer }}</span>
</x-wirekit::assistant-message>

Reasoning

The reasoning slot is collapsed behind a disclosure — the answer is the point, the thinking is opt-in.

An answer with its reasoning tucked away
Assistant atlas-2
Checked the rate-limit middleware, then the API docs, then confirmed against the 429 response headers.
Sixty requests a minute, per token — and the 429 tells you exactly how long to wait.

Metadata and actions

footer takes ambient chips (latency, tokens, cost); actions takes controls.

Footer chips and actions
Assistant atlas-2
Done — the import finished with 412 rows.
1.2s · 340 tokens

Props

Prop Type Default Description
role string 'assistant' assistant, user, system
name string|null null Speaker name; defaults to the role's wording
avatar string|null null Avatar image for the speaker
model string|null null Model chip — assistant turns only
intent string 'neutral' State tint on the bubble: neutral, info, success, warning, danger
streaming bool false Marks the turn aria-busy while tokens land
announce string 'sentence' sentence, all, off
scope string|null null Scoped personalization name

Slots

Slot Description
default The answer body (rendered as prose)
reasoning Collapsed disclosure above the answer
footer Ambient chips — latency, tokens, cost
actions Controls — copy, regenerate, rate

Accessibility

  • The body is never a live region. aria-live="off" on the streaming body is deliberate — a live region there re-reads the whole answer per token.
  • A separate, always-present announcer (role="status", aria-live="polite") receives complete sentences. It is rendered up front, because a live region created at the same moment as its text is inert to assistive technology.
  • streaming sets aria-busy="true" — assistive technology knows the turn is still being written.
  • Each turn is an <article> named by its speaker, so a screen reader can jump turn by turn.
  • A system turn has no avatar — it is not a person.
  • Icon-only actions need their own aria-label.

Keyboard Interaction

Key Action
Tab Focus the reasoning disclosure, then the actions
Enter / Space Toggle the reasoning disclosure

Pitfalls

  • Do not add your own aria-live to the body. That re-introduces exactly the stuttering re-read this component avoids.
  • Do not leave streaming true. A turn stuck aria-busy tells assistive technology the answer never finished.
  • Use flush() with announce="all". Without it, nothing is announced.

Design Tokens

Element Token
Assistant / system surface --color-wk-bg-elevated, --color-wk-bg-muted
User surface --color-wk-accent (10% mix over elevated)
Border --color-wk-border-subtle, --border-wk-width
Radius --radius-wk-lg
Speaker text --text-wk-sm, --font-wk-heading-weight
Footer chips --text-wk-xs, --color-wk-text-muted

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store