Detent web components
By Shane Logsdon

AI & chat

AI Response

Implemented

Production-ready streaming AI response with sources and actions

detent-ai-response

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

36.9 KB gzip for the class module

Variants

A finished answer with its code block, sources and actions, and the same component mid-stream (caret, live region, Streaming status). The answer uses the documented Markdown allow-list; incomplete links stay inert while streaming, and every source URL passes safeHref, so the second source in the answer above renders inert with a Link blocked for your safety notice. Text, sources and tool rows are a local simulation; nothing here calls a model or a network service.

Recorded states

  • focus-visible recorded state; no setter
  • loading recorded state; no setter
  • error recorded state; no setter
  • streaming recorded state; no setter
  • reduced motion recorded state; no setter
  • idle recorded state; no setter
  • generating recorded state; no setter
  • complete recorded state; no setter
  • cancelled recorded state; no setter

Usage

Import

import '/components/ai-response/define.js';

// or register it yourself
import { DetentAIResponse } from '/components/ai-response/ai-response.js';

Attributes

  • accent-color
  • code
  • content
  • default-expanded
  • filename
  • language
  • model-badge
  • model-name
  • status
  • timestamp
  • title
  • tool-count

Properties

  • active
  • d
  • onFeedback
  • onRegenerate
  • onToggleDetails
  • sources
  • tools

Slots

  • default
  • href
  • icon

Events

  • detent-regenerate — {}
  • detent-toggle-details — {}
  • detent-feedback — {}

Example

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

<detent-ai-response
  accent-color
  code
  content=""
  default-expanded
  filename
  language
  model-badge
  model-name="EasyAI 2.0"
  status="complete"
  timestamp
  title
  tool-count
></detent-ai-response>

Property-only values

const element = document.querySelector('detent-ai-response');
if (element !== null) {
  // sources: AIResponseSource[]
  element.sources = [];
  // onRegenerate: () => void
  element.onRegenerate = () => {};
  // onFeedback: like
  element.onFeedback = undefined;
}

Source

The pinned upstream source for this row:

  • src/components/ui/AIResponse.tsx sha256 78a10e1b65eb…

Native files

The native module and its artifacts:

  • src/components/ai-response/ai-response.ts
  • src/components/ai-response/styles.css
  • src/components/ai-response/define.ts
  • docs/components/ai-response.md
  • site/examples/ai-response.ts
  • tests/browser/ai-response.spec.ts
  • evidence/components/ai-response.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.
code———Recorded in the catalog as an attribute.
contentcontentstring''Kebab-case attribute; camelCase property.
default-expanded———Recorded in the catalog as an attribute.
filename———Recorded in the catalog as an attribute.
language———Recorded in the catalog as an attribute.
model-badge———Recorded in the catalog as an attribute.
model-namemodelNamestring'EasyAI 2.0'Kebab-case attribute; camelCase property.
statusstatus'idle' | 'generating' | 'streaming' | 'complete' | 'error' | 'cancelled''complete'Kebab-case attribute; camelCase property.
timestamp———Recorded in the catalog as an attribute.
title———Recorded in the catalog as an attribute.
tool-count———Recorded in the catalog as an attribute.

Properties

Properties
PropertyTypeDefaultNotes
active——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
d——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onFeedbacklikeundefinedProperty-only: assign from script; not accepted as an attribute.
onRegenerate() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onToggleDetails——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
sourcesAIResponseSource[][]Property-only: assign from script; not accepted as an attribute.
tools——Recorded in the catalog without a top-level type; pinned from source when the row is ported.

Events

Events
EventUpstream propPayload typeNotes
detent-regenerateonRegenerate() => voidComposed, bubbles; fires only for a user action.
detent-toggle-detailsonToggleDetails() => voidComposed, bubbles; fires only for a user action.
detent-feedbackonFeedback(type: "like" | "dislike") => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.
hrefConsumer content; rendered through a slot, never re-created.
iconConsumer content; rendered through a slot, never re-created.

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • content uses Marked 18.1.2 plus DOMPurify 3.4.17 with RETURN_DOM_FRAGMENT and the documented Markdown allow-list; raw HTML stays literal, refused URLs are inert, and incomplete link targets stay inert while streaming. The parser grammar and sanitization policy are recorded in docs/components/ai-response.md.
  • CodeBlock, AIResponseHeader, AIResponseContent, AIResponseSources, AIResponseToolsSummary and AIResponseActions ship as documented parts of the one tag (PC-2.1); safeHref ships as a module function and useAIResponseContext() becomes the element's own properties (a reader with no React context).
  • content / filename / language are the CodeBlock projection of the row: they render the code block when code is set.
  • d (an SVG path) and active (orb spin) from sourceOnlyProps are upstream internal render parameters with no native equivalent; the orb is a local SVG/CSS gradient whose spin is bound to status.
  • The pinned source has no syntax highlighter: code fences render as code text in a labelled pre/code block with the copy action.
  • Framer Motion's AnimatePresence/spring transitions become CSS transitions and animations on the shared duration tokens; no shared spring is used because every animated property here is decorative (caret opacity, skeleton opacity, orb rotation).
  • className is replaced by documented parts and custom properties; there is no Tailwind in consumers.

What it does

A streaming-ready assistant message: header with model orb and live status, the answer body, a code block with a copy trigger, expandable cited sources, a tools summary and feedback actions.

Upstream behaviour
  • Progressive streaming state with typing indicator and loading skeleton
  • Markdown rendering with headings, lists, blockquotes, tables, and code fences
  • Code blocks with dedicated one-click copy buttons
  • Composable cited sources list with expandable link pills and previews
  • Built-in user feedback actions (Copy, Regenerate, Like, Dislike)
  • Seamless integration with Agent Activity and tool execution traces
  • Fully accessible keyboard navigation and ARIA live regions for streaming updates
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • content uses Marked 18.1.2 plus DOMPurify 3.4.17 with RETURN_DOM_FRAGMENT and the documented Markdown allow-list; raw HTML stays literal, refused URLs are inert, and incomplete link targets stay inert while streaming. The parser grammar and sanitization policy are recorded in docs/components/ai-response.md.
  • CodeBlock, AIResponseHeader, AIResponseContent, AIResponseSources, AIResponseToolsSummary and AIResponseActions ship as documented parts of the one tag (PC-2.1); safeHref ships as a module function and useAIResponseContext() becomes the element's own properties (a reader with no React context).
  • content / filename / language are the CodeBlock projection of the row: they render the code block when code is set.
  • d (an SVG path) and active (orb spin) from sourceOnlyProps are upstream internal render parameters with no native equivalent; the orb is a local SVG/CSS gradient whose spin is bound to status.
  • The pinned source has no syntax highlighter: code fences render as code text in a labelled pre/code block with the copy action.
  • Framer Motion's AnimatePresence/spring transitions become CSS transitions and animations on the shared duration tokens; no shared spring is used because every animated property here is decorative (caret opacity, skeleton opacity, orb rotation).
  • className is replaced by documented parts and custom properties; there is no Tailwind in consumers.
Previous: AI Agent Activity Next: Matrix Clock

Docs

Components

Project