Skip to content

The default shell of <AparteChat> leaves you these places to put your own markup. The same slots exist on all four wrappers — that is checked mechanically on every pnpm gate, not promised in prose — and each framework declares them in its own idiom.

SlotReactVueSvelteAngular
composercomposer={…}<template #composer><svelte:fragment slot="composer">slot="composer"
empty-stateemptyState={…}<template #empty-state><svelte:fragment slot="empty-state">slot="empty-state"
above-composeraboveComposer={…}<template #above-composer><svelte:fragment slot="above-composer">slot="above-composer"
toolbartoolbar={…}<template #toolbar><svelte:fragment slot="toolbar">slot="toolbar"

Custom composer content, rendered inside <aparte-composer> in place of the default shell (input · send, plus the attachment primitives when attachments is set).

React:

tsx
<AparteChat composer={<MyThing />} />

Vue:

vue
<AparteChat>
  <template #composer><MyThing /></template>
</AparteChat>

Svelte:

svelte
<AparteChat>
  <svelte:fragment slot="composer"><MyThing /></svelte:fragment>
</AparteChat>

Angular:

html
<aparte-chat>
  <div slot="composer"><app-my-thing /></div>
</aparte-chat>

Welcome / placeholder content shown INSIDE the viewport while there are no messages (a real “empty state” region, not a workaround via aboveComposer).

React:

tsx
<AparteChat emptyState={<MyThing />} />

Vue:

vue
<AparteChat>
  <template #empty-state><MyThing /></template>
</AparteChat>

Svelte:

svelte
<AparteChat>
  <svelte:fragment slot="empty-state"><MyThing /></svelte:fragment>
</AparteChat>

Angular:

html
<aparte-chat>
  <div slot="empty-state"><app-my-thing /></div>
</aparte-chat>

Content rendered ABOVE the composer (e.g. a disclaimer banner, a “scroll to bottom” affordance, a context chip).

React:

tsx
<AparteChat aboveComposer={<MyThing />} />

Vue:

vue
<AparteChat>
  <template #above-composer><MyThing /></template>
</AparteChat>

Svelte:

svelte
<AparteChat>
  <svelte:fragment slot="above-composer"><MyThing /></svelte:fragment>
</AparteChat>

Angular:

html
<aparte-chat>
  <div slot="above-composer"><app-my-thing /></div>
</aparte-chat>

The composer’s bottom row — a mode picker, a model selector, a token counter.

React:

tsx
<AparteChat toolbar={<MyThing />} />

Vue:

vue
<AparteChat>
  <template #toolbar><MyThing /></template>
</AparteChat>

Svelte:

svelte
<AparteChat>
  <svelte:fragment slot="toolbar"><MyThing /></svelte:fragment>
</AparteChat>

Angular:

html
<aparte-chat>
  <div slot="toolbar"><app-my-thing /></div>
</aparte-chat>
PropReactVueSvelteAngular
loadingloading={…}:loadingloading[loading]

Draw the wait yourself while your store answers; the controller sets it for its own fetches.

The same events on all four wrappers, each in its own idiom. React, Vue and Angular hand you the payload directly. Svelte has a callback prop, with the payload, and a component event as well — on:messageSent and the rest — with it under event.detail.

PayloadReactVueSvelteAngular
AparteSendEventDetailonMessageSent@message-sentonmessageSent(messageSent)
AparteActionEventDetailonAction@actiononaction(action)
AparteMessage[]onMessagesChange@messages-changeonmessagesChange(messagesChange)
AparteMessageonMessageAppended@message-appendedonmessageAppended(messageAppended)
booleanonTypingChange@typing-changeontypingChange(typingChange)
stringonConversationCreated@conversation-createdonconversationCreated(conversationCreated)

Notification that the user submitted a message from the composer.

Fired when a custom bubble action (registered via aparteGlobalConfig.registerAction with zones: ['bubble']) is clicked — a typed wrapper over the bubbling aparte-action DOM event.

Fired when the active message path changes (branch navigation / edit / retry / streaming).

Fired when a message is appended internally (e.g. by AparteClient).

Fired when the typing/“thinking” indicator should toggle.

Fired when the controller lazily creates a conversation on first send.

A custom bubble is a render hook, not a slot: React takes a renderBubble function, Vue and Svelte a bubble slot that receives the message, and Angular a [bubbleTemplate] with the message as $implicit. It is per-message rather than per-region, which is why it is shaped differently — see Custom bubbles.

Placement inside a slot is yours. Nothing here is positional: put your controls in the order you want them and push one to the end with margin-inline-start: auto — a logical property, so it follows the reading direction. The worked example is in The composer toolbar.

provideAparte is Angular’s, and only Angular’s

Section titled “provideAparte is Angular’s, and only Angular’s”

Angular alone needs an app initializer that runs before the first component, and a DestroyRef to release the theme listener it registers. That is what provideAparte() is. There is no provideAparte in the other three, and that is not a gap: everything it configures is a plain function call — bar theme: 'auto', which is a matchMedia listener you own — so React, Vue and Svelte make the same calls at module scope, before the app mounts.

ts
import { aparteGlobalConfig } from '@aparte/core';
import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';
import { fr } from '@aparte/locale-fr';

aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));
aparteGlobalConfig.setModelConfig({ defaultModel: 'openai/gpt-4o-mini' });
aparteGlobalConfig.setLocale(fr);
document.documentElement.setAttribute('data-aparte-theme', 'dark');
// 'auto' is the one option with no one-liner: match prefers-color-scheme, rewrite the
// attribute on change, and drop the listener when your app tears down.
// The client stays the wrapper's own: useAparteClient() in React and Vue,
// createAparteClient() in Svelte.

The day a second wrapper needs that theme listener with a disposer of its own, applyThemeMode moves into @aparte/core and all four call it — one implementation, four idioms. Until then it lives where its one caller is.