`) for a fully custom bubble. Every imperative method (streaming, branch/edit, `scrollToBottom`) is mirrored on the `chat` store and reachable via `bind:this`.
The other five are `onaction`, `onmessagesChange`, `onmessageAppended`, `ontypingChange` and `onconversationCreated` (as events: `on:action`, `on:messagesChange`, … with the payload under `event.detail`) — the table with all four frameworks side by side is generated from the wrapper source: [Wrapper surface](/reference/wrappers/#callbacks).
## Wiring a real model
[Section titled “Wiring a real model”](#wiring-a-real-model)
The wrapper is **provider-agnostic**. Register a provider + transport once (see [Providers](/providers/)) and start an `AparteClient` with `createAparteClient` — it bridges composer sends to the model:
```svelte
chat.onMessagesChange(m)} />
```
Pass a per-instance `config` prop to scope providers/transport to a single `` instead of `aparteGlobalConfig`.
Note
`createAparteClient` accepts the full `AparteClientOptions`. To drive the chat with the **standalone agent loop** instead of core’s inline one, inject it: `createAparteClient({ streamRunner: runStreamAgent })` from [`@aparte/engine`](/guides/engine/) — an optional swap-in, not required. With the client mounted, switch the retry/edit buttons on — `aparteGlobalConfig.setBubbleActions({ retry: true, edit: true })`; they ship off because without a host they do nothing (see [What ships enabled](/guides/customization/#what-ships-enabled)). For file uploads add the `attachments` prop (off by default) — see [Attachments](/guides/attachments/). The `` presenter — what the built-in approval gate and `requestUserInput()` ask through — renders inside the host **by default**, as in ``; pass `elicitation={false}` when you register a presenter of your own. The root element (`[data-aparte-chat]`) takes `class` and `style`, merged after core’s own class, so `` sizes the chat column with utilities.
## Any aparté element: typed in the markup
[Section titled “Any aparté element: typed in the markup”](#any-aparté-element-typed-in-the-markup)
The `aparte-*` tags are declared through `SvelteHTMLElements`, so `svelte-check` covers both their attributes and their `on:` handlers — no `AparteUi` needed:
```svelte
use(e.detail.value)}
>
GPT-4o mini
```
On Svelte 5 in runes mode, `on:` on an element is the one form the compiler flags as deprecated (`event_directive_deprecated`); write the event attribute instead — `onaparte-select-change={(e) => …}` — which `svelte-check` types the same way.
Presence attributes take `''` to set and `null` to remove, never `false` — Svelte stringifies what it sets on a custom element, so `searchable={false}` would render `searchable="false"` and an element testing `hasAttribute` reads that as on. The rules and the full set are on [Placing elements, typed](/frameworks/elements/).
An element from a plugin, or one of your own
This typing covers `@aparte/core`’s elements — the ones the wrapper depends on. An element from a plugin (`aparte-model-selector`, from [`@aparte/plugin-model-selector`](/plugins/model-selector/)) or one of your own is typed by **whoever owns it**, never by us: a third-party plugin’s author cannot add a line to core, so shipping typing for our own plugins would privilege our packages over theirs.
See [your own element](/frameworks/elements/#your-own-element-or-a-plugins) for the two mechanisms — both are the same amount of work for us as for you.
## Any OTHER element: ``
[Section titled “Any OTHER element: \”](#any-other-element-aparteui)
For an element aparté does not define — one of yours, or a third party’s:
```svelte
console.log(e.type, e.detail)}
/>
```
`onelementEvent` receives the element’s own `CustomEvent`. The component event `on:elementEvent` still fires too, with that event under `e.detail` — the Svelte 4 spelling.
It mounts any tag name, which is what a foreign element needs and what aparté’s own no longer do.
## Also exported
[Section titled “Also exported”](#also-exported)
* `createConversationManager` — Svelte stores over the core `AparteConversationManager` (list / create / archive), for a multi-conversation sidebar.
## Testing it
[Section titled “Testing it”](#testing-it)
Vitest — every runner — executes on Node, so `@aparte/core` resolves to its DOM-free entry and no `` element upgrades under jsdom: the tag stays a plain `HTMLElement` and every assertion about it fails for a reason nothing explains. Alias the specifier to [`@aparte/core/browser`](/frameworks/elements/#testing-your-components), the entry with the elements in it.
# Vue 3 AI chat component, with composables — @aparte/vue
> The @aparte/vue wrapper — an ergonomic component plus composables over the aparté web components.
`@aparte/vue` wraps `@aparte/core` for Vue 3.5+: an ergonomic `` component, composables for state and the client, and a generic `` escape hatch.
```bash
npm install @aparte/vue @aparte/core vue
```
`@aparte/core` and `vue` are **peer dependencies**.
On the server
This wrapper carries **no** server guard. Under Nuxt, import it in a client-only context yourself — a custom element extends `HTMLElement` and cannot be constructed during an SSR pass. `@aparte/core` itself imports cleanly on a server through its DOM-free entry: see [On the server](/frameworks/elements/#on-the-server).
## `` + `useAparteChat`
[Section titled “\ + useAparteChat”](#apartechat--useapartechat)
The `useAparteChat` composable owns the `messages` ref and the component ref, so you bind them and skip the manual `@messages-change` → `messages` round-trip:
```vue
Ask me anything…
```
The user’s message is appended to the thread **automatically** on send — don’t add it yourself. `@message-sent` is optional and fires *after* that append, for side-effects only (scroll, analytics, a backend call).
Slots are named slots: `empty-state`, `composer`, `above-composer`, `toolbar` (the composer’s bottom row — mode picker, model selector: see [The composer toolbar](/guides/customization/#the-composer-toolbar) for an example), and the scoped `bubble` slot (`#bubble="{ message }"`) for a fully custom bubble. The imperative handle (`chat.chatRef`) exposes streaming, branch/edit and `scrollToBottom` — also available as plain methods straight off the `chat` object.
The six callbacks are `@message-sent`, `@action`, `@messages-change`, `@message-appended`, `@typing-change` and `@conversation-created`. Vue hands you the payload directly — the table with all four frameworks side by side is generated from the wrapper source: [Wrapper surface](/reference/wrappers/#callbacks).
## Wiring a real model
[Section titled “Wiring a real model”](#wiring-a-real-model)
The wrapper is **provider-agnostic**. Register a provider + transport once (see [Providers](/providers/)) and mount an `AparteClient` with `useAparteClient` — it bridges composer sends to the model:
```vue
```
Pass a per-instance `config` prop to scope providers/transport to a single `` instead of `aparteGlobalConfig`.
Note
`useAparteClient` accepts the full `AparteClientOptions`. To drive the chat with the **standalone agent loop** instead of core’s inline one, inject it: `useAparteClient({ streamRunner: runStreamAgent })` from [`@aparte/engine`](/guides/engine/) — an optional swap-in, not required. With the client mounted, switch the retry/edit buttons on — `aparteGlobalConfig.setBubbleActions({ retry: true, edit: true })`; they ship off because without a host they do nothing (see [What ships enabled](/guides/customization/#what-ships-enabled)). For file uploads add the `attachments` prop (off by default) — see [Attachments](/guides/attachments/). The `` presenter — what the built-in approval gate and `requestUserInput()` ask through — renders inside the host **by default**, as in ``; pass `:elicitation="false"` when you register a presenter of your own. `class` and `style` fall through to the root element (`[data-aparte-chat]`), Vue’s default for a single-root component, so `` sizes the chat column with utilities.
## Any aparté element: typed in the template
[Section titled “Any aparté element: typed in the template”](#any-aparté-element-typed-in-the-template)
The `aparte-*` tags are declared through Vue’s `GlobalComponents`, so `vue-tsc` checks them in any template once the package is imported — no `AparteUi`, no `isCustomElement` guesswork about names:
```vue