Chat
Lab Available in release assets
Compose a server-owned conversation transcript and native message form with bounded realtime fragment updates.
Chat primary example
<section class="thin-chat" id="support-chat" aria-labelledby="chat-title">
<header>
<h1 id="chat-title">Support conversation</h1>
<p>Server-owned conversation with 2 messages.</p>
</header>
<ol class="thin-messages" id="message-list">
<li id="message-101">
<article>
<header>
<strong>Ryan</strong>
<a href="/conversations/support/messages/101"><time datetime="2026-08-01T19:30:00Z">2:30 PM</time></a>
</header>
<p>Can you confirm the deployment window?</p>
</article>
</li>
<li id="message-102">
<article>
<header>
<strong>Support</strong>
<a href="/conversations/support/messages/102"><time datetime="2026-08-01T19:32:00Z">2:32 PM</time></a>
</header>
<p>Yes โ the server has recorded 4:00 PM Central.</p>
</article>
</li>
</ol>
<form action="/api/advanced/chat?conversation=support" method="post" data-on:submit="@post('/api/advanced/chat?conversation=support')">
<label class="thin-field">Message<textarea class="thin-textarea" name="message" rows="3" required maxlength="2000" aria-describedby="message-hint">
</textarea>
</label>
<small id="message-hint">Enter sends only when the application explicitly opts in; this example uses the submit button.</small>
<button class="thin-button">Send message</button>
</form>
</section>Show code
<section class="thin-chat" id="support-chat" aria-labelledby="chat-title">
<header>
<h1 id="chat-title">Support conversation</h1>
<p>Server-owned conversation with 2 messages.</p>
</header>
<ol class="thin-messages" id="message-list">
<li id="message-101">
<article>
<header>
<strong>Ryan</strong>
<a href="/conversations/support/messages/101"><time datetime="2026-08-01T19:30:00Z">2:30 PM</time></a>
</header>
<p>Can you confirm the deployment window?</p>
</article>
</li>
<li id="message-102">
<article>
<header>
<strong>Support</strong>
<a href="/conversations/support/messages/102"><time datetime="2026-08-01T19:32:00Z">2:32 PM</time></a>
</header>
<p>Yes โ the server has recorded 4:00 PM Central.</p>
</article>
</li>
</ol>
<form action="/api/advanced/chat?conversation=support" method="post" data-on:submit="@post('/api/advanced/chat?conversation=support')">
<label class="thin-field">Message<textarea class="thin-textarea" name="message" rows="3" required maxlength="2000" aria-describedby="message-hint">
</textarea>
</label>
<small id="message-hint">Enter sends only when the application explicitly opts in; this example uses the submit button.</small>
<button class="thin-button">Send message</button>
</form>
</section>Anatomy
- Stable conversation or transcript region
- Ordered server-owned message list
- Durable message IDs and permalinks
- Named native message form
- Server status/error response
Variants and sizes
- Support conversation
- Direct conversation
- Group conversation
Gallery coverage: No structured variant fixtures are published yet; the primary example above is the only verified production markup.
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
- Initial history
- Pending send
- Sent
- Validation error
- Send failure
- Disconnected
- Reconnecting
- Duplicate replay
- Long message
- Empty conversation
- Permission revoked
Structured state fixtures are not published for this item yet.
Interaction and keyboard behavior
Initial history and pagination come from the server. Submit posts a native form; the server validates membership, content, ordering, and persistence before returning the authoritative transcript/composer. SSE may append or replace by durable message ID. Reconnect resumes from a server cursor and deduplicates replayed IDs; incoming messages never move focus or force scroll when the reader has moved away.
Accessibility
Use an ordered transcript with authors and machine-readable times, a labelled textarea, text errors, and a real submit button. Announce only newly appended messages after initial load; do not replay the whole transcript. Keep focus in the composer after a successful send, move it to an error only when useful, and never steal focus for incoming messages.
- Contract
LIVE- Basis
- Native labelled list and form with status/error content; not an APG Feed claim until feed loading, article relationships, and focus/scroll rules exist
- Evidence
tests/unit/library/chat-messages.test.ts
API
Use .thin-chat on a stable identified region and .thin-messages on the ordered transcript. The application owns authentication, membership, retention, moderation, cursors, ordering, idempotency, and delivery truth. Datastar/SSE is bounded transport only.
Behavior tier: datastar-required
Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.
Fallback
The server-rendered transcript, message permalinks, and ordinary submitted message form remain complete without Datastar, SSE, or JavaScript.
Assets and dependencies
/labs.css
Dependencies: datastar@1.0.2
Source: packages/ui/src/items/chat/style.ts
Composition
Derived from the examples and style specs, not authored.