Skip to content

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'
On this page
Open

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.

Open

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.

ts
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.

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.

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'.

FieldTypeRequiredDescription
subTypestringyesWhich view inside the custom renderer draws this segment (e.g. ‘weather’, ‘poll’, ‘onboarding’).
dataunknown—Arbitrary data payload for the component

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.