Skip to main content
WireKit
Copy for LLM

Stream

The <x-wirekit::stream> component renders streaming text output — an LLM response, a live log — from a Server-Sent Events endpoint. It exists so you never re-write the three parts that are genuinely hard to get right:

  • Accessibility. A growing aria-live="polite" field is unusable — a screen reader re-reads the whole thing on every token. Stream announces that a response is generating once, then the result once, and keeps the visible output out of the live region so it never floods.
  • Reduced motion. A token-by-token build is motion. Under prefers-reduced-motion: reduce the tokens are buffered and revealed as one block.
  • Abort & error. A half-streamed response whose connection drops has a defined terminal state (aborted / failed), not a silent freeze.

You supply the URL and render the text; the library owns the state machine.

Usage

Point url at an SSE endpoint that emits text tokens and closes with a done signal ([DONE] by default). The stream starts on load and appends tokens as they arrive.

<x-wirekit::stream url="/chat/{{ $conversation->id }}/stream" />

The demos on this page use simulate — they type a fixed string out token by token from a local timer, with no live endpoint, so you can watch the streaming behavior right here. Use the ↻ Replay control to run it again. In your app you pass url instead; simulate is for demos and typewriter effects.

Watch it stream

With controls

The default slot renders inside the component's Alpine scope, so you can add Stop and Retry controls that call stop() / restart() and react to status. Bind your control buttons with WireKit's own components.

Stream with stop / retry controls

Announcement mode

By default (announce="result") the settled response is announced once in full. For long outputs where reading the whole thing aloud is too much, use announce="status" to announce only that the response is ready — the text stays on screen for the reader to navigate to.

Status-only announcement

Seeded output (SSR / resume)

Pass initialText to render a completed response immediately — resume a finished conversation on a server render, or show a static example — without opening a stream. Combine it with :auto-start="false" so the component stays idle and simply displays the seeded text.

A seeded, already-complete response

Livewire integration

In your app, point url at a Livewire-driven route that returns text/event-stream and writes tokens as your model produces them; drive the trigger from Livewire and let the component own the rendering and accessibility:

{{-- 1. A route that streams tokens as Server-Sent Events, ending with [DONE]. --}}
<x-wirekit::stream url="/chat/{{ $conversation->id }}/stream" />

Props

Prop Type Default Description
url string|null null SSE endpoint to stream from. Null → the component stays idle.
eventName string 'message' SSE event name to listen for.
doneSignal string '[DONE]' Payload that ends the stream.
announce string 'result' 'result' announces the final text once; 'status' announces only readiness.
autoStart bool true Open the stream on init. false → start it from your own control.
initialText string|null null Seed text — resume a completed response (SSR) or show a static example.
simulate string|null null Stream this text token by token from a local timer, with no url — a live-looking demo or a typewriter effect.
simulateSpeed int|null 55 Milliseconds per token in simulate mode.
scope string|null null Scoped personalization key.

The Alpine scope exposes text, status (idle / streaming / done / aborted / failed), the derived isStreaming / isDone / isAborted / isFailed / isTerminal, and the methods start(), stop(), and restart() — bind them from the default slot for controls and custom state UIs. The error slot overrides the default failure message.

Accessibility

  • One live region. A single visually-hidden role="status" aria-live="polite" region announces generating once, then the result (or ready) once. The visible output is deliberately not a live region, so a screen reader is never re-read on every token — the single most important decision this primitive makes.
  • Reduced motion. Under prefers-reduced-motion: reduce, tokens are buffered and the full response is revealed at once — the incremental build is suppressed. The streaming caret only pulses when motion is allowed.
  • Defined terminal states. Abort and connection loss resolve to aborted / failed, and the failure surfaces in a role="alert" region — never a silent hang.

Keyboard Interaction

The output itself has no interaction model — it is streamed text. Any controls you add in the default slot (Stop, Regenerate) are ordinary buttons: Tab to reach them, Enter / Space to activate. There is nothing bespoke to learn.

Design Tokens

Token Used for
--text-wk-md / --color-wk-text / --font-wk-sans Streamed output type
--color-wk-text-muted Streaming caret
--color-wk-danger-text Failure message
--gap-wk-xs Vertical rhythm between output, caret, and controls

Further Reading