Chat
Chat uses the current DuskMoonUI design: rounded user bubbles, unboxed agent answers, native work disclosures, a waiting-message tray, and a compact composer. Styles come from @duskmoon-dev/core; local adjustments connect those styles across Shadow DOM and slots.
Installation
npm install @duskmoon-dev/el-chat
Usage
import '@duskmoon-dev/el-chat/register';
<el-dm-chat
agent
align="start"
avatar="AI"
author="Assistant"
time="Just now"
status="sent"
content="I can help with **markdown** content."
>
</el-dm-chat>
Live Demo
Full Conversation
Current Agent Design
Use agent for an unboxed assistant answer. Put activity in an el-dm-chat-work assigned to
slot="work"; closing it leaves the answer visible. Existing bubble messages remain available
by omitting agent.
<el-dm-chat agent author="Assistant" content="The final **answer** stays visible.">
<el-dm-chat-work slot="work" summary="Worked · 2 tool calls">
<el-dm-chat-reasoning summary="Thinking">Reviewing the request.</el-dm-chat-reasoning>
<el-dm-chat-tool name="search" status="success"></el-dm-chat-tool>
</el-dm-chat-work>
</el-dm-chat>
<el-dm-chat align="end" color="primary" collapsible content="A long user message…"></el-dm-chat>
collapsible limits a bubble to six rendered lines. Longer content gets a native Show more /
Show less button, with aria-controls and aria-expanded. The element rechecks wrapped text
when its content or width changes; short messages need no toggle. expanded controls the open
state. Agent answers remain uncollapsed.
The example starts with an active reply and two waiting messages. Steer inserts a waiting message into the current conversation without starting another reply. Complete reply advances the example queue. Queue state and dispatch belong to the application.
Scroll-Driven Transcript
Wrap messages in el-dm-chat-scroll and assign each assistant message a unique timeline from
1 through 24. User messages remain in the transcript but do not receive indicators. The rail
uses DuskMoonUI Scroll-Driven Animations when supported; its buttons remain a functional fallback
and always scroll only the transcript panel.
Give the wrapper an explicit height so its body can overflow.
<el-dm-chat-scroll style="height: 22rem">
<el-dm-chat agent timeline="1" author="Assistant" content="First assistant reply"></el-dm-chat>
<el-dm-chat align="end" author="You" content="A user message"></el-dm-chat>
<el-dm-chat agent timeline="2" author="Assistant" content="Second assistant reply"></el-dm-chat>
</el-dm-chat-scroll>
Properties
el-dm-chat and el-dm-chat-bubble
| Property | Type | Default | Description |
|---|---|---|---|
agent |
boolean |
false |
Unboxed assistant layout (el-dm-chat only) |
collapsible |
boolean |
false |
Collapse bubbles exceeding six rendered lines |
expanded |
boolean |
false |
Reveal the complete collapsible bubble |
align |
'start' | 'end' |
'start' |
Message alignment |
color |
'primary' | 'secondary' | 'tertiary' | 'info' | 'success' | 'warning' | 'error' |
- | Bubble color |
size |
'xs' | 'sm' | 'md' | 'lg' |
- | Bubble size |
variant |
'tonal' | 'filled' |
'tonal' |
Bubble visual style |
streaming |
boolean |
false |
Shows a streaming caret |
avatar |
string |
- | Avatar text fallback |
author |
string |
- | Header author text |
time |
string |
- | Footer timestamp |
status |
string |
- | Footer delivery status |
actions |
string |
- | Comma-separated icon actions |
content |
string |
- | Markdown content to render |
timeline |
1 through 24 |
- | Assistant indicator mapping (el-dm-chat only) |
el-dm-chat-scroll
| Property | Type | Default | Description |
|---|---|---|---|
label |
string |
'Conversation' |
Accessible transcript-region label |
indicator-label |
string |
'Assistant replies' |
Indicator navigation and button prefix |
Timeline values must be unique. When duplicate values are supplied, the last eligible assistant
message wins. The generated navigate event is cancelable and contains
{ timeline, index, target }.
el-dm-chat-input
| Property | Type | Default | Description |
|---|---|---|---|
name |
string |
'' |
Form field name |
value |
string |
'' |
Markdown input value |
placeholder |
string |
'Message the agent…' |
Input placeholder |
disabled |
boolean |
false |
Disables the input and send |
readonly |
boolean |
false |
Makes the input read-only |
send-label |
string |
'Send' |
Send button label |
clear-on-send |
boolean |
false |
Clears text and local files after sending |
busy |
boolean |
false |
Label the primary action Queue and enable draft steering |
color |
semantic color or ghost |
- | DuskMoonUI composer color |
auto-grow |
boolean |
false |
Grow the editor with content up to its maximum height |
Local attachments are exposed directly on the chat input. getFiles() returns a snapshot, and
clearFiles() removes all locally attached files.
Set --dm-chat-editor-min-height on the chat input to size both the outer editor and its nested
markdown editor. The compact default is 3rem. With auto-grow, the editor expands as text is entered,
then its write area scrolls at --dm-chat-editor-max-height (default 24rem).
el-dm-chat-work
| Property | Type | Default | Description |
|---|---|---|---|
summary |
string |
'Work' |
Activity summary; counts and labels come from the application |
open |
boolean |
false |
Open the native work disclosure |
el-dm-chat-queue
| Property | Type | Default | Description |
|---|---|---|---|
items |
ChatQueueItem[] |
[] |
Ordered queue items; set the property or a JSON attribute |
label |
string |
'Queued messages' |
Accessible queue name |
Each item has { id, content, active?, attachment?, attachmentAlt? }. Keep id stable and mark
the next item active. attachment is an optional thumbnail URL; provide descriptive
attachmentAlt text. Empty queues are hidden. Row controls emit steer and remove with
{ item, index }; the element leaves the array under application control.
<el-dm-chat-input busy color="ghost" auto-grow>
<el-dm-chat-queue slot="queue" label="Waiting messages"></el-dm-chat-queue>
</el-dm-chat-input>
queue.items = [{ id: 'draft-1', content: 'Check accessibility too.', active: true }];
queue.addEventListener('steer', ({ detail: { item } }) => {
addUserMessageToCurrentConversation(item.content);
queue.items = queue.items.filter((queued) => queued.id !== item.id);
});
The composer emits send for its primary action; the application can send immediately or queue
while busy. steer on the composer carries { value, files } for the current draft. Enter
submits, Shift+Enter preserves a newline, and Ctrl/Cmd+Enter steers while busy (or sends while idle).
IME confirmation never submits. Blank drafts require an attachment. Both draft events are
cancelable: call preventDefault() to retain the draft and attachments, including when awaiting
an asynchronous admission result. Clear accepted asynchronous drafts through setValue() and
clearFiles() after acknowledgment, preserving any later edits. focus() focuses the editor.
el-dm-chat-reasoning
| Property | Type | Default | Description |
|---|---|---|---|
summary |
string |
'Reasoning' |
Default summary label |
open |
boolean |
false |
Opens the details by default |
el-dm-chat-tool
| Property | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Tool name |
status |
'pending' | 'running' | 'success' | 'error' |
'pending' |
Tool call status |
open |
boolean |
false |
Opens the details by default |
Bubble Variants
Bubble Colors
<el-dm-chat-bubble color="primary" content="Primary **markdown** response"></el-dm-chat-bubble>
<el-dm-chat-bubble color="success" content="Success response"></el-dm-chat-bubble>
<el-dm-chat-bubble align="end" color="info" variant="filled" content="Filled info response">
</el-dm-chat-bubble>
Markdown Content
Use the content attribute or property for markdown-rendered message bodies.
Markdown Message
<el-dm-chat
agent
avatar="AI"
author="Assistant"
content="Rendered **markdown** with `inline code`."
></el-dm-chat>
Chat Input
Markdown Chat Input
<el-dm-chat-input placeholder="Message the agent…" send-label="Send" clear-on-send>
</el-dm-chat-input>
<script>
document.querySelector('el-dm-chat-input').addEventListener('send', (event) => {
const { value, files } = event.detail;
// Encode or upload the File objects before forwarding a serializable payload.
console.log(value, files);
});
</script>
For a compact composer that starts around two to three lines:
<el-dm-chat-input
auto-grow
style="--dm-chat-editor-min-height: 4.5rem; --dm-chat-editor-max-height: 16rem"
></el-dm-chat-input>
Status and Quick Actions
Message Status and Actions
<el-dm-chat
agent
avatar="AI"
author="Assistant"
time="12:06"
status="streaming"
actions="Stop, Copy"
content="I am writing a response and exposing icon quick actions."
></el-dm-chat>
Reasoning
Place reasoning and tool elements inside a message with the reasoning and tools slots. They
render before the final markdown answer, and a streaming caret stays attached to that final answer.
Reasoning Inside a Message
Checked the current DuskMoonUI chat contract before updating the custom element.
npm info @duskmoon-dev/core version1.20.6Chat tests passed<el-dm-chat agent streaming content="The final **answer** streams here.">
<el-dm-chat-reasoning slot="reasoning" summary="Reasoning" open>
Checked package metadata before making the change.
<el-dm-chat-tool slot="tools" name="package-search" status="success"></el-dm-chat-tool>
</el-dm-chat-reasoning>
<el-dm-chat-tool slot="tools" name="build" status="success"></el-dm-chat-tool>
</el-dm-chat>
Tool Calls
Tool Call Status
rg “chat” packages/docsdocs build completed<el-dm-chat-tool name="search" status="running" open>
<code slot="call">rg "chat" packages/docs</code>
</el-dm-chat-tool>
Slots
| Element | Slot | Description |
|---|---|---|
el-dm-chat-scroll |
default | Direct el-dm-chat transcript rows |
el-dm-chat |
default | Final markdown bubble content |
el-dm-chat |
work |
Activity disclosure before the final answer |
el-dm-chat |
reasoning |
Reasoning shown before the final answer |
el-dm-chat |
tool |
Single direct tool call |
el-dm-chat |
tools |
Direct tool calls |
el-dm-chat |
avatar |
Avatar shown beside the message |
el-dm-chat |
header |
Metadata above the bubble |
el-dm-chat |
footer |
Metadata below the bubble |
el-dm-chat |
actions |
Custom quick action controls |
el-dm-chat-bubble |
reasoning |
Reasoning shown before the final answer |
el-dm-chat-bubble |
tool |
Single direct tool call |
el-dm-chat-bubble |
tools |
Direct tool calls |
el-dm-chat-input |
queue |
Waiting queue attached above the composer |
el-dm-chat-work |
default | Work records, reasoning, and tool calls |
el-dm-chat-work |
summary |
Custom native disclosure summary |
el-dm-chat-reasoning |
default | Reasoning body |
el-dm-chat-reasoning |
summary |
Custom summary content |
el-dm-chat-reasoning |
tool |
Single tool call in reasoning |
el-dm-chat-reasoning |
tools |
Tool calls in reasoning |
el-dm-chat-tool |
default | Extra tool body content |
el-dm-chat-tool |
name |
Custom tool header name |
el-dm-chat-tool |
call |
Tool call input |
el-dm-chat-tool |
result |
Tool result output |
CSS Parts
| Part | Description |
|---|---|
scroll |
Scrollable transcript region |
track |
Sticky indicator rail |
indicator |
Generated assistant-reply button |
body |
Transcript, work, or reasoning body |
chat |
Chat row container |
avatar |
Avatar wrapper |
header |
Header metadata |
bubble |
Message bubble |
bubble-content |
Final answer inside the bubble |
toggle |
Show more / Show less button |
footer |
Footer metadata |
status |
Message status |
actions |
Quick actions wrapper |
action |
Generated icon action button |
content |
Agent answer or rendered markdown |
input |
Chat input wrapper |
composer |
Core composer surface |
editor |
Markdown input element |
send |
Send button |
steer |
Composer Steer button |
queue |
Waiting-message tray |
list |
Ordered queue list |
item |
Queued message row |
work |
Native work disclosure |
summary |
Work or reasoning summary |
reasoning |
Reasoning details container |
tools |
Reasoning tool-call wrapper |
tool |
Tool details container |
typing |
Typing indicator |
Events
| Element | Event | Detail |
|---|---|---|
el-dm-chat-scroll |
navigate |
{ timeline, index, target } |
el-dm-chat |
quick-action |
{ action, label } |
el-dm-chat-input |
send |
{ value, files } |
el-dm-chat-input |
steer |
{ value, files } |
el-dm-chat-queue |
steer, remove |
{ item, index } |
Cancel navigate with event.preventDefault() to replace the built-in in-panel scrolling.