Chat

Chat integration patterns: floating launcher, embedded with sidebar, and full-page layouts using existing VB primitives.

Overview

These patterns compose <chat-window> with existing VB primitives to create complete chat experiences. No new custom elements are needed — <dialog>, commandfor, and <layout-sidebar> handle the shell.

Floating Launcher

A fixed-position FAB button opens a <dialog> containing chat-window. The commandfor/command attributes handle open/close automatically with proper focus management.

<style> .chat-trigger { position: fixed; inset-block-end: var(--size-xl); inset-inline-end: var(--size-xl); z-index: 1000; width: 56px; height: 56px; border-radius: var(--radius-full); background: var(--color-primary); color: var(--color-text-on-primary); border: none; cursor: pointer; display: grid; place-items: center; box-shadow: 0 4px 16px oklch(0% 0 0 / 0.2); transition: transform var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default); } .chat-trigger:hover { transform: scale(1.06); box-shadow: 0 6px 20px oklch(0% 0 0 / 0.28); } .chat-trigger:active { transform: scale(0.96); } </style> <!-- Fixed trigger button --> <button commandfor="chat-dialog" command="show-modal" class="chat-trigger" aria-label="Open chat"> <icon-wc name="message-circle"></icon-wc> </button> <!-- Dialog contains chat-window --> <dialog id="chat-dialog"> <chat-window endpoint="/api/chat"> <script type="application/json" data-participants> { "user": { "name": "You", "role": "user" }, "assistant": { "name": "Assistant", "role": "agent" } } </script> <header> <h3>AI Assistant</h3> <button commandfor="chat-dialog" command="close" class="ghost small" aria-label="Close"> <icon-wc name="x"></icon-wc> </button> </header> <chat-thread role="log" aria-label="Chat" aria-live="polite"> </chat-thread> <chat-input name="message"> <textarea data-grow rows="1" placeholder="Ask anything..."></textarea> <footer> <button type="submit" class="small" data-send aria-label="Send"> <icon-wc name="send"></icon-wc> </button> </footer> </chat-input> </chat-window> </dialog>

Unread Badge

Add an unread count using <layout-badge> positioned over the trigger button.

<button commandfor="chat-dialog" command="show-modal" class="chat-trigger" aria-label="Open chat"> <icon-wc name="message-circle"></icon-wc> <layout-badge data-color="error" data-size="sm" style="position:absolute;inset-block-start:-4px;inset-inline-end:-4px"> 3 </layout-badge> </button>

Embedded with Sidebar

For always-visible chat panels, use <layout-sidebar> with a chat history list in the aside. The sidebar collapses on narrow viewports.

<layout-sidebar data-layout-side="start" data-layout-sidebar-width="narrow"> <aside> <nav aria-label="Chat history"> <a href="/chat/5" aria-current="true">Current conversation</a> <a href="/chat/4">Refund request</a> <a href="/chat/3">Setup help</a> </nav> </aside> <chat-window endpoint="/api/ai"> <script type="application/json" data-participants> { "user": { "name": "You", "role": "user" }, "assistant": { "name": "Assistant", "role": "agent" } } </script> <header> <h3>Support Chat</h3> </header> <chat-thread role="log" aria-label="Support conversation" aria-live="polite"> </chat-thread> <chat-input name="message"> <textarea data-grow rows="1" placeholder="Type a message..."></textarea> <footer> <button type="submit" class="small" data-send aria-label="Send"> <icon-wc name="send"></icon-wc> </button> </footer> </chat-input> </chat-window> </layout-sidebar>

Full-Page with History Drawer

A full-viewport chat using height: 100dvh on the window. The history drawer uses <dialog data-position="start"> for a slide-in panel triggered from the header.

<chat-window endpoint="/api/ai" style="height: 100dvh"> <script type="application/json" data-participants> { "user": { "name": "You", "role": "user" }, "assistant": { "name": "Assistant", "role": "agent" } } </script> <header> <button commandfor="chat-history" command="show-modal" class="ghost small" aria-label="History"> <icon-wc name="sidebar"></icon-wc> </button> <h3>Chat</h3> <select data-model-select aria-label="Select model"> <option value="claude-sonnet-4-6">Sonnet 4.6</option> <option value="claude-haiku-4-5">Haiku 4.5</option> </select> </header> <chat-thread role="log" aria-label="Conversation" aria-live="polite"> </chat-thread> <chat-input name="message"> <textarea data-grow rows="1" data-max-rows="8" placeholder="Ask anything..."></textarea> <footer> <button type="submit" class="small" data-send aria-label="Send"> <icon-wc name="send"></icon-wc> </button> </footer> </chat-input> </chat-window> <dialog id="chat-history" data-position="start"> <header> <h3>History</h3> <button commandfor="chat-history" command="close" class="ghost small"> <icon-wc name="x"></icon-wc> </button> </header> <nav aria-label="Previous conversations"> <a href="/chat/5" aria-current="true">Current conversation</a> <a href="/chat/4">Refund request</a> <a href="/chat/3">Setup help</a> </nav> </dialog>

Key Primitives Used

Need Primitive Why
Launcher trigger commandfor / command="show-modal" Native focus management, no JS
Chat overlay <dialog> Modal with backdrop, Escape to close
History drawer <dialog data-position="start"> Slide-in panel with VB styling
Split layout <layout-sidebar> Responsive sidebar collapse
Unread count <layout-badge> Color-coded badge overlay

Related