Chat status
<aparte-chat-status>
A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns
it on: the framework host only ever flips it back OFF (on the first streamed token),
and the four wrappers render one inside the viewport driven by their own isTyping
prop.
It dispatches nothing, deliberately: it reports, it does not ask.
Use it for a state only the app knows about — “Searching the docs…”, “Uploading…”,
a queue position. It is NOT the indicator for the gap between a send and the first
token: that one is built into the bubble (.aparte-waiting, shown while streaming
is set on a non-user bubble that has nothing to display yet). Turning this element on
for that gap is how a page ends up showing two indicators for one wait.
It does not project children: _render() writes the subtree — a
.aparte-status-container row holding an empty avatar div and an .aparte-body that
wraps .aparte-status-content — so markup authored between the tags does not
survive. That avatar div never gets contents here, and .aparte-avatar:empty is
display: none, so it is not a spacer: the line sits flush with the row padding
rather than indented under an assistant bubble’s text column.
The seam for custom contents is setStatusRenderer, scoped or global: the container
keeps owning show/hide (data-visible), the accessible name (aria-label) and its
.aparte-message row metrics whatever the renderer returns, but the pulsing dot and
the text node belong to the default path only.
A text attribute renders the visible label and feeds the accessible name; with no
text the line is dots-only and the name falls back to the literal Typing — this
element does not read the locale. The container is a polite live region, and a live
region announces its CONTENT, not its aria-label: so the word also goes INTO the
region, in the visible span when text is set and in a screen-reader-only span when
it is not. Exactly one of the two is ever populated — both would be read twice. That
fallback word is driven by visible, not written once at render: a live region is
announced when its content CHANGES while it is exposed, and every wrapper mounts this
element hidden and flips the attribute — so a word written at render time is a word
the region already held when it appeared, the reveal-from-display: none path, which
is the one screen readers are documented not to announce reliably.
Hiding happens twice over: the host element is
display: none without [visible], and data-visible drives the fade/translate on
the container.
The config is resolved live rather than cached, so a setStatusRenderer call that
lands after this element has already upgraded still reaches it: the element
re-renders on aparte-config-change, filtered to its own config.
The two borrowed row variables below have one scope caveat: inside a viewport
narrower than 520px core REASSIGNS --aparte-message-padding-block and
--aparte-message-padding-inline on .aparte-message itself, so a declaration on
this host element loses to them there.
Example
Section titled “Example”<!-- The app owns this indicator: core turns it on for nobody, which is also why it is the wrong tool for the wait before the first token — the bubble's built-in waiting state already covers that one. --><aparte-chat-status visible text="Searching the docs…"></aparte-chat-status>Attributes
Section titled “Attributes”| Attribute | Description |
|---|---|
visible | Shows or hides the indicator. |
text | The line to show. Absent, the line is dots-only and the accessible name falls back to the literal Typing (not the locale’s string). |
Methods
Section titled “Methods”| Method | Description |
|---|---|
show(): void | Show the typing indicator |
hide(): void | Hide the typing indicator |
toggle(): void | Toggle visibility |
isVisible(): boolean | Check if visible |
Theming
Section titled “Theming”Override any of these on :root, on a subtree, or on one instance — custom properties inherit downward. Some are this element’s own; others are site-wide tokens that also style it, and overriding one of those at :root moves everything that reads it. The full set is in the CSS variables reference.
<aparte-chat-status>
Section titled “<aparte-chat-status>”| Variable | Default | Description |
|---|---|---|
--aparte-status-color | var(--aparte-text-muted) | Colour of the label text and of the pulsing dot in the default line. |
--aparte-status-font-size | var(--aparte-font-size-md) | Size of the visible label (italic by default) in the default line. |
--aparte-status-dot-size | 6px | Diameter of the single pulsing dot in the default line. |
--aparte-message-padding-block | var(--aparte-space-8) | Vertical padding of the row, read because the container also carries .aparte-message — the status line borrows a bubble’s row metrics so it lines up with the transcript. |
--aparte-message-padding-inline | var(--aparte-space-6) | Horizontal padding of the row, same reason. |
--aparte-message-max-width | 800px | Width cap of that same row. |
In a framework
Section titled “In a framework”The element is the same object everywhere — the tag does not change. What changes is how an attribute is written and how an event reaches you.
<aparte-chat-status visible=""></aparte-chat-status><aparte-chat-status visible=""></aparte-chat-status>The aparte-* tags are typed JSX intrinsics as soon as you import from @aparte/react. A presence attribute takes '', never true — React stringifies it, and visible={false} would render visible="false", which hasAttribute reads as on.
<template> <aparte-chat-status visible=""></aparte-chat-status></template>Declared through Vue’s GlobalComponents, so vue-tsc checks the tag in any template. A presence attribute takes '' to set and null to remove, never false.
<aparte-chat-status visible=""></aparte-chat-status>Declared through SvelteHTMLElements, so svelte-check covers the attributes and the on: handlers. A presence attribute takes '', never false.
import { AparteChatStatusDirective } from '@aparte/angular';<aparte-chat-status [visible]="true"></aparte-chat-status>A standalone directive whose selector IS the tag, so the real element sits in the template — @if, @for and content projection all reach it — and no CUSTOM_ELEMENTS_SCHEMA is needed.
Installation and the framework-specific traps: React · Vue · Svelte · Angular.