Detent web components
By Shane Logsdon

AI & chat

Chat

Implemented

Conversation history, composer and message states

detent-chat

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-data
Group
AI & chat

9.7 KB gzip for the class module

Variants

No messages yet — ask something to start the thread.

A full conversation (thread sidebar, grouped history, attachments, delivery states and retry) plus the empty state using the empty-placeholder slot from light DOM. Below 768px the sidebar becomes a drawer: the header toggle opens it, Escape or the backdrop closes it and returns focus. Replies are a local simulation — no model or service is called.

Ready.

Recorded states

  • error recorded state; no setter
  • closed recorded state; no setter
  • streaming recorded state; no setter
  • completed recorded state; no setter

Usage

Import

import '/components/chat/define.js';

// or register it yourself
import { DetentChat } from '/components/chat/chat.js';

Attributes

  • accent-color
  • active-thread-id
  • allow-attachments
  • is-generating
  • model-badge
  • placeholder
  • subtitle
  • title

Properties

  • message
  • messages
  • onClearChat
  • onNewChat
  • onRetry
  • onSelectThread
  • onSendMessage
  • threads

Slots

  • default
  • empty-placeholder

Events

  • detent-send-message — {}
  • detent-select-thread — {}
  • detent-new-chat — {}
  • detent-clear-chat — {}
  • detent-retry — {}

Example

Runnable example: site/examples/chat.ts. The snippet below is generated from the catalogued attributes.

<detent-chat
  accent-color
  active-thread-id
  allow-attachments
  is-generating
  model-badge
  placeholder
  subtitle
  title
></detent-chat>

Property-only values

const element = document.querySelector('detent-chat');
if (element !== null) {
  // messages: ChatMessageItem[]
  element.messages = [];
  // onSendMessage: (content: string, attachments?: ChatAttachment[]) => void
  element.onSendMessage = () => {};
  // threads: ChatConversationThread[]
  element.threads = [];
  // onSelectThread: (threadId: string) => void
  element.onSelectThread = () => {};
  // onNewChat: () => void
  element.onNewChat = () => {};
  // onClearChat: () => void
  element.onClearChat = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/Chat.tsx sha256 de36dd7312c4…

Native files

The native module and its artifacts:

  • src/components/chat/chat.ts
  • src/components/chat/styles.css
  • src/components/chat/define.ts
  • docs/components/chat.md
  • site/examples/chat.ts
  • tests/browser/chat.spec.ts
  • evidence/components/chat.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
accent-color———Recorded in the catalog as an attribute.
active-thread-idactiveThreadIdstringundefinedKebab-case attribute; camelCase property.
allow-attachments———Recorded in the catalog as an attribute.
is-generatingisGeneratingbooleanfalsePresence semantics: present means true, absent means false.
model-badge———Recorded in the catalog as an attribute.
placeholder———Recorded in the catalog as an attribute.
subtitle———Recorded in the catalog as an attribute.
title———Recorded in the catalog as an attribute.

Properties

Properties
PropertyTypeDefaultNotes
message——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
messagesChatMessageItem[][]Property-only: assign from script; not accepted as an attribute.
onClearChat() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onNewChat() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onRetry——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onSelectThread(threadId: string) => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onSendMessage(content: string, attachments?: ChatAttachment[]) => voidundefinedProperty-only: assign from script; not accepted as an attribute.
threadsChatConversationThread[]undefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-send-messageonSendMessage(content: string, attachments?: ChatAttachment[]) => voidComposed, bubbles; fires only for a user action.
detent-select-threadonSelectThread(threadId: string) => voidComposed, bubbles; fires only for a user action.
detent-new-chatonNewChat() => voidComposed, bubbles; fires only for a user action.
detent-clear-chatonClearChat() => voidComposed, bubbles; fires only for a user action.
detent-retryonRetry(messageId: string) => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.
empty-placeholderConsumer content; rendered through a slot, never re-created.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The ChatHeader, ChatSidebar, ChatMessage, ChatMessages and ChatComposer exports ship as documented parts of the one detent-chat tag (PC-2.1 keeps one tag per catalog row). useChat() becomes useChat(element), a live reader, because a custom element has no React context; it throws a TypeError for anything that is not an detent-chat element.
  • message (one ChatMessageItem) is the native projection of the ChatMessage subcomponent and is appended after messages, so a streaming reply can be updated in place without rebuilding the transcript.
  • activeThreadId is an attribute-backed _initial/controlled_ value, but a user click also updates the owned state and emits detent-select-thread (upstream is fully controlled and needs the consumer to re-render). The catalog records the controlled/uncontrolled mapping.
  • Delivery status gets a visible text badge; upstream shows only a caret (streaming) and a failure line, so sending/sent/delivered were colour-only. This is a recorded accessibility correction (PC-12.6).
  • Upstream's ad hoc fenced-code presentation inside assistant messages ( blocks and inline code splitting) is not ported: R-2.1 forbids a regex markdown renderer and R-2.3 keeps such source as text. Message content renders with white-space: pre-wrap, so fenced code is still readable as text.
  • system messages get the author "System" instead of upstream's "Assistant" fallback, so the author line matches the recorded role.
  • Framer Motion springs/AnimatePresence become CSS transitions (drawer slide, caret blink, spinner), all removed under prefers-reduced-motion: reduce.
  • className is replaced by the documented parts and custom properties; there is no Tailwind in consumers.

What it does

A complete conversation surface: an optional grouped thread-history sidebar that becomes an off-canvas drawer on mobile, a transcript with role avatars, authors, timestamps, delivery badges, attachments and copy/retry actions, and an auto-growing composer with Enter-to-send, Shift+Enter-for-newline and file attachments.

Upstream behaviour
  • Complete conversational layout with optional historical threads sidebar and responsive mobile drawer
  • Rich message components supporting user, assistant, and system roles with timestamps and avatars
  • Built-in file and image attachment upload system with chips, sizes, and removal actions
  • Auto-expanding multiline composer with Enter-to-send and Shift+Enter for newline shortcuts
  • Message delivery states: sending, sent, delivered, failed (with retry), and streaming
  • Accessible action triggers for copying message content and managing conversation state
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The ChatHeader, ChatSidebar, ChatMessage, ChatMessages and ChatComposer exports ship as documented parts of the one detent-chat tag (PC-2.1 keeps one tag per catalog row). useChat() becomes useChat(element), a live reader, because a custom element has no React context; it throws a TypeError for anything that is not an detent-chat element.
  • message (one ChatMessageItem) is the native projection of the ChatMessage subcomponent and is appended after messages, so a streaming reply can be updated in place without rebuilding the transcript.
  • activeThreadId is an attribute-backed _initial/controlled_ value, but a user click also updates the owned state and emits detent-select-thread (upstream is fully controlled and needs the consumer to re-render). The catalog records the controlled/uncontrolled mapping.
  • Delivery status gets a visible text badge; upstream shows only a caret (streaming) and a failure line, so sending/sent/delivered were colour-only. This is a recorded accessibility correction (PC-12.6).
  • Upstream's ad hoc fenced-code presentation inside assistant messages ( blocks and inline code splitting) is not ported: R-2.1 forbids a regex markdown renderer and R-2.3 keeps such source as text. Message content renders with white-space: pre-wrap, so fenced code is still readable as text.
  • system messages get the author "System" instead of upstream's "Assistant" fallback, so the author line matches the recorded role.
  • Framer Motion springs/AnimatePresence become CSS transitions (drawer slide, caret blink, spinner), all removed under prefers-reduced-motion: reduce.
  • className is replaced by the documented parts and custom properties; there is no Tailwind in consumers.
Previous: Car Smoke Page Transition Next: Circular Orbit

Docs

Components

Project