Framework-agnostic AI Chat Web Components. Drop an
<aparte-chat> onto any page — React, Vue, Svelte, Angular, or none at all —
wire it to any LLM through a pluggable transport, and restyle everything with CSS
variables. Zero third-party dependencies at the core.
See how it compares with assistant-ui, Deep Chat and Loquix.
🚀 Beta on npm. Every
@aparte/*package ships together at one version under thebetatag —npm i @aparte/core@beta(the badge above is the live number). The number itself is a plain0.x: what says beta is the npm tag, the leading zero and this notice — not a-betasuffix. The declared surface is frozen as of 0.17.0 and leaves only through a notice release; what changed when is at apartejs.dev/changelog (or CHANGELOG.md here), and the docs live at apartejs.dev.
pronounced ah-par-té — French aparté: a line spoken aside, a private word taken "in aparté". Also reads as a part: one composable piece.
- Framework-agnostic core. One engine — vanilla Web Components — renders the
same chat everywhere. Thin wrappers give React/Vue/Svelte/Angular an
ergonomic component; with no framework, use the
<aparte-*>custom elements directly. - Zero third-party dependencies in
@aparte/core. Markdown, syntax highlighting, model pickers — all opt-inprovider-*/plugin-*packages. The core stays tiny. - Bring your own model, your way. A transport seam decides where the
request goes:
AparteDirectTransport(browser-direct — BYOK or a local model) orAparteBackendTransport(your/api/chat, key stays server-side). Providers cover the OpenAI-compatible family, the Vercel AI SDK (25+ vendors), and in-browser Transformers.js. - Streaming, typed segments, tools. Replies stream as typed segments — text,
markdown, code, chain-of-thought — including a
tool_callsegment with a built-in human-in-the-loop approve/reject gate. - Yours to restyle. Theme everything through CSS variables (no forking), swap icons and render hooks, and localise the UI strings (English built-in, French shipped).
- A library, not an app. No routing, settings, or persistence baked in, and backend-agnostic — it's a chat surface you compose, not a product you inherit.
npm install @aparte/core @aparte/provider-openai-compat<aparte-chat center-empty placeholder="Ask anything…" style="height: 600px"></aparte-chat>import '@aparte/core'; // registers the <aparte-*> custom elements
import '@aparte/core/styles.css'; // theme variables + component styles
import { registerDefaultRenderers, aparteGlobalConfig, AparteClient, AparteDirectTransport } from '@aparte/core';
import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';
registerDefaultRenderers();
// A local model (LM Studio / Ollama) needs no key — just enable CORS in the app.
// Swap in presets.OPENAI / .MISTRAL / .OPENROUTER (+ a keyResolver) for a cloud vendor.
aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.LMSTUDIO));
aparteGlobalConfig.setTransport(new AparteDirectTransport({ byok: true }));
new AparteClient().start(); // listens for sends, streams the reply into the chat
// Retry/edit only work with a host like the client above, so core ships them off:
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true });
// The bare shell streams the assistant reply; echo the user's own message in:
const chat = document.querySelector('aparte-chat')!;
chat.addEventListener('aparte-send', (e) =>
chat.viewport?.appendMessage({ id: crypto.randomUUID(), role: 'user', content: e.detail.content, timestamp: Date.now() }),
);A real streaming, bring-your-own-key chat — no backend, no build magic. → Getting started
Same core, an ergonomic component per framework — and the wrapper owns the state and the user bubble, so there's even less to wire:
npm install @aparte/react @aparte/core react react-domimport { AparteChat, useAparteChat, useAparteClient } from '@aparte/react';
import { aparteGlobalConfig, AparteDirectTransport } from '@aparte/core';
import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';
import '@aparte/core/styles.css';
aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));
aparteGlobalConfig.setTransport(new AparteDirectTransport({ byok: true }));
export function Chat() {
const chat = useAparteChat();
useAparteClient(); // bridges composer sends to the model
return <AparteChat ref={chat.ref} messages={chat.messages} onMessagesChange={chat.setMessages} centerWhenEmpty />;
}| Framework | Package | Guide |
|---|---|---|
| React 18 / 19 | @aparte/react |
React |
| Vue 3 | @aparte/vue |
Vue |
| Svelte | @aparte/svelte |
Svelte |
| Angular 19 | @aparte/angular |
Angular |
Runnable examples for every framework (plus vanilla) live in
apps/examples.
| Package | What |
|---|---|
@aparte/core |
Vanilla web components — the chat engine, zero third-party dependencies |
@aparte/engine |
Framework-agnostic agent loop (runStreamAgent) |
@aparte/react · /vue · /svelte · /angular |
Thin, ergonomic framework wrappers (peer deps) |
@aparte/provider-openai-compat |
One adapter for every OpenAI-compatible endpoint (OpenAI, Mistral, OpenRouter, Groq, LM Studio, Ollama…) |
@aparte/provider-ai-sdk |
Vercel AI SDK bridge (Anthropic, Google, 25+ vendors) |
@aparte/provider-transformers |
In-browser inference via Transformers.js |
@aparte/provider-scenario |
A scripted model — replays turns you wrote, for demos, docs and your own tests |
@aparte/docs-mcp |
The docs as an MCP server — npx @aparte/docs-mcp lets your coding agent search and read them |
@aparte/plugin-marked · -streaming-markdown · -shiki |
Markdown rendering + syntax highlighting |
@aparte/plugin-model-selector · -ask-user · -approval · -compaction |
A provider/model picker; a question-elicitation UI; approval modes (plan / ask / auto-edit / auto); conversation compaction |
@aparte/locale-fr |
French UI strings (English is core's built-in default) |
Live at apartejs.dev — a Starlight
site in apps/docs (run pnpm run docs locally):
- Getting started — your first streaming chat, no framework
- Providers — connect a real model (OpenAI-compatible, AI SDK, Transformers.js)
- Theming — restyle everything through CSS variables
- Customization — icons, render hooks, action registries
- Conversations & branching — retry, edit, branches, persistence
- The agent engine — the headless
runStreamAgentloop - Tools & human-in-the-loop — function calling with an approve/reject gate
- Backend transport — your
/api/chat, key stays server-side - Bring your own loop — display-only mode for an external agent loop
Beta, built in the open. The core, engine, four wrappers, providers, plugins and six runnable examples are on npm, green across the unit suite and a browser E2E suite (Chromium, WebKit and Firefox, accessibility gated with axe-core). The declared surface is frozen; what is still open before the first stable release is on the roadmap.
Every @aparte/* package is released together, at one version — install any of
them at the same number. What shipped when: CHANGELOG.md (the
aggregate), or a package's own CHANGELOG.md for its detail.
Issues and PRs welcome — see CONTRIBUTING.md for conventions and the gate each change lands behind. It's a pnpm + NX monorepo:
pnpm install
pnpm build # all packages
pnpm test # unit suite (Vitest)
pnpm e2e # browser smoke E2E (Playwright; run pnpm e2e:install once)
pnpm run docs # the docs site (bare `pnpm docs` triggers npm's builtin instead)MIT © Paul Richez