Custom view
A view of your own inside a reply — a card, a chart, a form — drawn by a renderer you register for a segment type you name.
-
type: 'custom'
<!-- In your app: import { registerSegmentRenderer } from '@aparte/core'. -->
<aparte-chat-bubble message-id="a1" data-role="assistant" name="Assistant"></aparte-chat-bubble>
<script>
const { registerSegmentRenderer } = window.aparte;
// A type of your own, drawn by a renderer of your own: here a small forecast card.
registerSegmentRenderer({
type: 'forecast',
render(segment) {
const card = document.createElement('div');
card.className = 'aparte-card';
const head = Object.assign(document.createElement('div'), { className: 'aparte-card__header', textContent: segment.city });
const body = Object.assign(document.createElement('div'), { className: 'aparte-card__body' });
body.textContent = segment.days.map((d) => `${d.day} ${d.high}°`).join(' · ');
card.append(head, body);
return card;
},
});
document.querySelector('aparte-chat-bubble').setSegments([
{ id: 's1', type: 'text', content: 'The next three days in Lille:' },
{ id: 's2', type: 'forecast', city: 'Lille', days: [{ day: 'Thu', high: 14 }, { day: 'Fri', high: 16 }, { day: 'Sat', high: 12 }] },
]);
</script>A reply is a list of segments, and the list is open: give a segment a type of your own, register a renderer for that type, and the reply draws your view in its place. This is how a chat shows a weather card, a chart or a booking form in the middle of an answer (what other libraries call generative UI).
A view drawn from a tool’s result is the same idea one step further; the Tool call
page shows registerToolRenderer for that case.
Variants
Section titled “Variants”Several small views behind one renderer
Section titled “Several small views behind one renderer”<aparte-chat-bubble message-id="a1" data-role="assistant" name="Assistant"></aparte-chat-bubble>
<script>
const { registerSegmentRenderer } = window.aparte;
// type: 'custom' keeps several small views behind ONE renderer: switch on subType,
// and let the default branch draw something sensible for one you do not know.
registerSegmentRenderer({
type: 'custom',
render(segment) {
const el = document.createElement('span');
if (segment.subType === 'status') {
el.className = `aparte-badge aparte-badge--soft aparte-badge--${segment.data.ok ? 'success' : 'danger'}`;
el.textContent = segment.data.label;
} else {
el.textContent = segment.fallback ?? '';
}
return el;
},
});
document.querySelector('aparte-chat-bubble').setSegments([
{ id: 's1', type: 'text', content: 'Checks on the release branch:' },
{ id: 's2', type: 'custom', subType: 'status', data: { ok: true, label: 'Build passed' } },
{ id: 's3', type: 'custom', subType: 'status', data: { ok: false, label: '1 e2e test failed' } },
]);
</script>The custom type keeps a family of small views behind a single renderer: each segment says which
view it wants in subType, and carries its data in data. Draw something sensible in the
default branch, for a sub-type your app does not know yet.
import { registerSegmentRenderer } from '@aparte/core';
import type { AparteSegmentBase } from '@aparte/core';
interface ForecastSegment extends AparteSegmentBase {
type: 'forecast';
city: string;
days: { day: string; high: number }[];
}
registerSegmentRenderer<ForecastSegment>({
type: 'forecast',
render(segment) {
const card = document.createElement('div');
card.className = 'aparte-card';
card.textContent = `${segment.city}: ${segment.days.map((d) => `${d.day} ${d.high}°`).join(', ')}`;
return card;
},
});render returns an element or an HTML string. Returning an element is safer: nothing you
interpolate goes through innerHTML. Three optional hooks follow it: setup once the view is in
the page, update when a streamed segment changes, cleanup when it leaves.
When no renderer is there
Section titled “When no renderer is there”A conversation can outlive the code that drew it: replayed in another app, exported, loaded
before a lazy renderer. Give the segment a fallback, one sentence, and core draws that text in
its place. When AparteClient builds the history sent back to the model, a segment of your own
contributes its content, or else that sentence.
Fields
Section titled “Fields”Every segment has id, type and an optional fallback; a segment of your own adds whatever
fields its view needs. The custom type adds these:
AparteCustomSegment, discriminated by type: 'custom'.
| Field | Type | Required | Description |
|---|---|---|---|
subType | string | yes | Which view inside the custom renderer draws this segment (e.g. ‘weather’, ‘poll’, ‘onboarding’). |
data | unknown | — | Arbitrary data payload for the component |
Accessibility
Section titled “Accessibility”What you draw is yours to make accessible: give a chart a text equivalent, a button a name, a form its labels. The kit’s recipes used in these demos (Card, Badge) already carry their contrast and focus styles.
Related
Section titled “Related”- Tool call — a view for a tool’s result.
- Customization guide — every rendering hook in one place.