Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Chat integration patterns: floating launcher, embedded with sidebar, and full-page layouts using existing VB primitives.
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.
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>
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>
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>
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>
| 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 |
<chat-window> — The orchestrating web component<chat-input> — Form-associated input<chat-thread> — Scrollable message container<dialog> — Native modal/drawer<layout-sidebar> — Split panel layout