Customize the chat
Change more than colours and sizes — which part of the chat each seam changes, when a change shows on the chats already on screen, and how two chats get different setups.
<!-- The same message twice. The first bubble is the one core draws; the second is a
shell of your own, scoped to that bubble alone by an instance config. A shell is markup:
the bubble finds the hooks it knows by class and fills them, so the name, the
content and the action bar keep working. The waiting indicator is the one hook whose
inside is yours to write: the dots below are markup, not something the bubble adds. -->
<div class="two-up">
<aparte-chat-bubble
message-id="a1"
data-role="assistant"
name="Assistant"
content="Retry does not overwrite a reply: it forks a sibling, and the picker walks them."
></aparte-chat-bubble>
<div id="custom"></div>
</div>
<style>
.two-up { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.my-card {
border-inline-start: 3px solid var(--aparte-primary);
border-radius: var(--aparte-radius-md);
background: var(--aparte-surface-1);
padding: var(--aparte-space-3);
}
.my-card__who { margin-block-end: var(--aparte-space-2); font-size: var(--aparte-font-size-sm); color: var(--aparte-text-muted); }
</style>
<script>
const { AparteConfig, attachConfig } = window.aparte;
// One config for this column only; the first bubble keeps the global one.
const mine = new AparteConfig();
mine.setBubbleShellRenderer((ctx) => `
<div class="aparte-message my-card" data-role="${ctx.role}">
<header class="my-card__who"><span class="aparte-name"></span></header>
<div class="aparte-message-content">
<div class="aparte-segments"></div>
<div class="aparte-content"></div>
<!-- The dots are yours to draw: the bubble only shows and hides this region. -->
<div class="aparte-waiting" hidden>
<span class="aparte-dots" aria-hidden="true">
<span class="aparte-dot"></span><span class="aparte-dot"></span><span class="aparte-dot"></span>
</span>
<span class="aparte-sr-only"></span>
</div>
</div>
<div class="aparte-footer"><div class="aparte-action-bar"></div></div>
</div>`);
const custom = document.querySelector('#custom');
attachConfig(custom, mine);
// Created AFTER the boundary: a bubble renders its shell once, when it connects.
const bubble = document.createElement('aparte-chat-bubble');
bubble.setAttribute('message-id', 'a2');
bubble.setAttribute('content', 'Retry does not overwrite a reply: it forks a sibling, and the picker walks them.');
custom.appendChild(bubble);
bubble.setAttribute('name', 'Assistant'); // the bubble writes it into .aparte-name
</script>Pick the seam
Section titled “Pick the seam”| You want to | Use | Page |
|---|---|---|
| change colours, sizes and shapes | CSS variables | Theme |
| replace the error card, an attachment chip, the status line | a render hook | Render hooks |
| redraw the whole message | a frame of your own, or an element of your own | The message |
| turn on retry and edit, add a button to a message or to the composer | the actions | Buttons |
| put a mode picker or a model selector under the composer | the composer’s toolbar | The composer toolbar |
| show a chart, a map or a form in a reply | a segment renderer | Your own content |
| show “Thought for 8s” | the timing a segment carries | Durations |
| swap the icons, add avatars | the icon and avatar providers | Icons and avatars |
| render Markdown and colour code | the Markdown and highlight plugins | Markdown and code |
Everything goes through the configuration
Section titled “Everything goes through the configuration”import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setErrorRenderer(({ message }) => {
const el = document.createElement('div');
el.className = 'my-error';
el.textContent = message;
return el;
});
aparteGlobalConfig.setErrorRenderer(null); // the built-in one againThe render hooks, the providers and the button settings are methods on aparteGlobalConfig, the
configuration every chat on the page reads; a render hook or the avatar provider takes null to
put the built-in back. A change to the buttons, the icons, the avatars or the language reaches the
messages already on screen, and a new status-line renderer reaches the line already shown. So do
the Markdown and highlight providers: a message drawn before them is drawn again with them. The
message frame, the error card, the attachment chips and a segment renderer apply to what is
drawn after them, so set those before the chat renders.
Custom segment types
Section titled “Custom segment types”import { registerSegmentRenderer } from '@aparte/core';
registerSegmentRenderer({
type: 'chart',
render(segment) {
const el = document.createElement('div');
el.className = 'my-chart';
return el;
},
});A reply is made of typed parts — text, reasoning, code, a tool call — called segments, and a
renderer registered for a type of your own draws it: a chart, a map, a form, generative UI. A
segment whose type no renderer claims shows its fallback text, or [Unknown segment type: …]
with a warning in the console when it has none: register a renderer for the type, or give the
segment a fallback. Your own content shows how such a segment
reaches a message, and the rest of a renderer’s life.
Per-chat configuration
Section titled “Per-chat configuration”import { AparteConfig, attachConfig } from '@aparte/core';
const support = new AparteConfig();
support.setBubbleActions({ retry: true, edit: true });
const chat = document.querySelector<HTMLElement>('#support-chat');
if (chat) attachConfig(chat, support);Attach a configuration of its own to a chat, or to an element around it, and every part inside
reads that one; detachConfig takes it off. The framework packages take it as their config
prop. A new configuration starts from the built-in defaults: set up on it what that chat needs —
its Markdown and highlighting (setupMarkedProvider(undefined, support)), its icons, its
language. It inherits one thing from the page-wide one, the segment renderers registered there,
and cannot remove one of those; registerSegmentRenderer(renderer, support) registers one for
that chat alone, which wins over the page-wide one.
- Render hooks Your own markup for one piece: the error card, an attachment chip, the status line.
- Your own content A chart, a map or a form in a reply, and a tag the model writes turned into one.
- Buttons Retry, edit and feedback, and buttons of your own on a message or in the composer.
- Icons and avatars Your icon set, and an avatar beside each message.