Skip to content

Thread

The chat thread is the scrolling conversation surface that pairs with the composer. It renders compact user bubbles, full-width assistant markdown (headings, lists, tables, code), per-message meta/copy controls, a streaming cursor, and a Claude-style centered empty state. It is themed through the global --ds-* tokens, so it follows data-theme automatically.

The thread root uses flex: 1 and height: 100%, so place it inside a height-constrained parent (for example a cmp-app-shell__content).

Each turn is an <article> with the cmp-chat__message class plus a --user or --assistant modifier. User turns use the accent cmp-chat__bubble--user; assistant turns render trusted markdown into cmp-chat__content--assistant. The copy control uses an inline SVG icon.

Can you summarize the syllabus policies?

38 chars

Key policies

Here is a short overview:

  • Late work loses 10% per day.
  • Two excused absences are allowed.
ItemWeight
Quizzes20%
Final30%
214 chars

Thinking…

<div
class="cmp-chat"
style="height: 24rem; border: 1px solid #dedede; border-radius: 0.5rem"
>
<div class="cmp-chat__messages">
<article class="cmp-chat__message cmp-chat__message--user">
<div class="cmp-chat__bubble cmp-chat__bubble--user">
<p>Can you summarize the syllabus policies?</p>
</div>
<div class="cmp-chat__meta cmp-chat__meta--end">
<button
type="button"
class="cmp-chat__copy-btn"
aria-label="Copy message"
>
<svg
class="cmp-chat__copy-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
></path>
</svg>
</button>
<span class="cmp-chat__char-count">38 chars</span>
</div>
</article>
<article class="cmp-chat__message cmp-chat__message--assistant">
<div class="cmp-chat__content cmp-chat__content--assistant">
<h3>Key policies</h3>
<p>Here is a short overview:</p>
<ul>
<li>Late work loses 10% per day.</li>
<li>Two excused absences are allowed.</li>
</ul>
<table>
<thead>
<tr>
<th>Item</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>Quizzes</td>
<td>20%</td>
</tr>
<tr>
<td>Final</td>
<td>30%</td>
</tr>
</tbody>
</table>
</div>
<div class="cmp-chat__meta cmp-chat__meta--start">
<button
type="button"
class="cmp-chat__copy-btn"
aria-label="Copy message"
>
<svg
class="cmp-chat__copy-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
></path>
</svg>
</button>
<span class="cmp-chat__char-count">214 chars</span>
</div>
</article>
<article class="cmp-chat__message cmp-chat__message--assistant">
<div
class="cmp-chat__content cmp-chat__content--assistant cmp-chat__content--thinking"
>
<p>Thinking…</p>
</div>
</article>
</div>
</div>

Before the first message, render cmp-chat--empty to vertically center branding (cmp-chat__welcome) above a centered composer (cmp-chat__composer--center).

ID Assistant

How can I help you build your course today?

<div
class="cmp-chat cmp-chat--empty"
style="height: 22rem; border: 1px solid #dedede; border-radius: 0.5rem"
>
<div class="cmp-chat__center">
<div class="cmp-chat__welcome">
<h2 class="cmp-chat__welcome-title">ID Assistant</h2>
<p class="cmp-chat__welcome-subtitle">
How can I help you build your course today?
</p>
</div>
<div class="cmp-chat__composer cmp-chat__composer--center">
<form class="js-chat-composer cmp-chat-composer" onsubmit="return false;">
<textarea
class="cmp-chat-composer__input"
rows="1"
placeholder="Type a message... (Enter to send, Shift+Enter for new line)"
aria-label="Message"
></textarea>
<div class="cmp-chat-composer__toolbar">
<div class="cmp-chat-composer__tools"></div>
<div class="cmp-chat-composer__actions">
<button
type="submit"
class="cmp-chat-composer__action cmp-chat-composer__action--send"
aria-label="Send"
>
<svg
class="cmp-chat-composer__icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
ClassElementPurpose
cmp-chatdivFull-height flex-column conversation root.
cmp-chat--emptydivCenters content for the pre-conversation state.
cmp-chat__messagesdivScrolling list of turns.
cmp-chat__messagearticleOne turn; pair with --user / --assistant.
cmp-chat__bubble--userdivRounded accent bubble for a user turn.
cmp-chat__content--assistantdivFull-width rendered assistant markdown.
cmp-chat__content--thinkingdivMuted, italic streaming placeholder.
cmp-chat__metadivPer-message meta row; --start / --end aligns.
cmp-chat__copy-btnbuttonCopy-message control.
cmp-chat__copy-iconsvgCopy glyph inside the copy button.
cmp-chat__char-countspanCharacter-count label.
cmp-chat__timestamptimeOptional message timestamp (--user right-aligns).
cmp-chat__cursorspanBlinking streaming caret.
cmp-chat__footerdivOptional footer note below the thread.
cmp-chat__composerdivWrapper around the docked composer.
cmp-chat__centerdivVertically centered empty-state column.
cmp-chat__welcomedivCentered branding block.
cmp-chat__welcome-titleh2Greeting title.
cmp-chat__welcome-subtitlepGreeting subtitle.
cmp-chat__composer--centerdivCentered composer variant (no docked separator).
  • Render each turn as an <article> and only inject assistant markdown from a trusted, sanitized source.
  • Icon-only controls (copy) require an aria-label; mark the <svg> aria-hidden="true".
  • Maintain readable contrast for assistant tables and code blocks in both themes.

Uses --ds-bg, --ds-fg, --ds-fg-muted, --ds-border, --ds-surface-muted, --ds-accent, --ds-accent-fg, and the table tokens --ds-table-head-bg / --ds-table-stripe-bg. Set data-theme="dark" on an ancestor to switch themes.