4.65.2

Chat layout

A standard layout for chat-driven, conversational experiences, assembled from core Skylab components.

Overview

Chat-driven experiences (Avi, Aviator, prototyping surfaces) each need the same visual scaffolding: a scrollable conversation history above a fixed composer. Rather than ship a single monolithic component, Skylab provides the primitives to assemble one. This page documents the standard composition so teams stop rolling their own.

The layout is built entirely from existing core components:

Region Component Role
Assistant message <s-box> Container for a single assistant turn of plain text or simple content.
Structured answer <s-tray> Groups related boxes into a single contiguous answer, e.g. steps or grouped results.
Highlighted answer <s-card-ai> Emphasizes an AI-generated result with a header, body, and optional footer actions.
Processing state <s-ai-processing> Animated thinking indicator that streams the response text once it arrives.
User message .chat-bubble Right-aligned bubble for the user's own turns. Styling shown below.

Demo

Send a message to see the composer append a user turn and Avi respond with an <s-ai-processing> indicator that transitions from thinking to a streamed answer.

Anatomy

The layout is a flex column with two regions:

  1. Conversation log - a scrollable, flex-grow region (overflow-y: auto) holding one .chat-turn per message. Assistant turns align to the start; user turns align to the end. Rich answers use .chat-turn--wide so cards and trays span the full width.
  2. Composer - a fixed-height footer holding the input and send control. It never scrolls with the log.

Styles

The <style> block is embedded in the demo above — copy it to get the full source including styles. Component regions (box, tray, card-ai, ai-processing) are styled by Skylab; only the shell, alignment, and user bubble are app-owned.

Behavior

Sending a message appends a user turn, then an assistant turn containing an <s-ai-processing> that is driven through its phases. Adapt the streamed content to your own data source:

function chatSend() { const input = document.getElementById('chat-input'); const log = document.getElementById('chat-log'); const text = input.value.trim(); if (!text) return; const userTurn = document.createElement('div'); userTurn.className = 'chat-turn chat-turn--user'; const bubble = document.createElement('div'); bubble.className = 'chat-bubble'; bubble.textContent = text; userTurn.appendChild(bubble); log.appendChild(userTurn); input.value = ''; const aiTurn = document.createElement('div'); aiTurn.className = 'chat-turn chat-turn--assistant chat-turn--wide'; const box = document.createElement('s-box'); const ai = document.createElement('s-ai-processing'); ai.thoughts = ['Reviewing your question...', 'Checking your tax data...', 'Preparing a response...']; ai.responseText = 'Based on your current filings, here is what I found and the next steps you can take.'; ai.thoughtDuration = 1500; box.appendChild(ai); aiTurn.appendChild(box); log.appendChild(aiTurn); requestAnimationFrame(() => { log.scrollTop = log.scrollHeight; }); ai.phase = 'thinking'; setTimeout(() => { ai.phase = 'typing'; requestAnimationFrame(() => { log.scrollTop = log.scrollHeight; }); }, 3000); } function chatKeydown(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); chatSend(); } }

Guidelines

Messages

Keep assistant turns in an <s-box> for plain replies. Reach for <s-card-ai> only when a response deserves emphasis or carries actions, and <s-tray> when several boxes form one grouped answer. Give rich answers .chat-turn--wide so they are not constrained to the bubble width.

Processing and streaming

Represent in-flight responses with <s-ai-processing>. Drive it through thinking then typing as your backend streams; it transitions to done and fires s-complete on its own. Scroll the log to the bottom whenever a turn is added.

Accessibility

<s-ai-processing> carries its own role="status" live region, so screen readers hear each phase without extra markup. Label the composer input (aria-label or a visible label) and the icon-only send control (aria-label="Send message"). Enter sends; Shift+Enter inserts a newline.

Design

Design resources can be found on the Skylab design documentation site: skylab.avalara.com