@chai-ui/vue
v1.4.1
Published
Chai UI Vue components — smart, drop-in UI for AI apps: Composer, Media Analyzer, Result Card, Edit Card, Side Panel, and the useComposer composable.
Maintainers
Readme
@chai-ui/vue
Chai UI for Vue: drop-in components for the input and output surfaces of
generative-media apps: a prompt composer, a media-to-prompt analyzer, a result card and an
edit card, with the AI work built in and wired to Fal.ai, OpenRouter, Vercel
AI Gateway or your own engine. Everything is controlled: you own the state with
v-model, and the components render it.
These are the same components as @chai-ui/react
and @chai-ui/svelte,
with the same behavior. All three are thin layers over
@chai-ui/core and run one shared test suite, including the WCAG 2.2 AA
checks (axe, keyboard and focus).
Building with it, or pointing a coding agent at it? Read
DESIGN.md (shipped in the package) for the usage rules,
and the docs site's llms.txt for
the API. The code boxes on chai-ui.com/docs
switch between React, Vue and Svelte.
Install
pnpm add @chai-ui/vue @chai-ui/core @chai-ui/tokensVue 3.5 or later, Nuxt included. No Chai-specific build setup.
// main.ts: tokens first, then the component styles that read them
import "@chai-ui/tokens/css";
import "@chai-ui/vue/style.css";Your own font
style.css loads Roboto, Material Design 3's typeface. If your app has its
own font, import @chai-ui/vue/style-without-fonts.css in its place (the
same styles, without Roboto) and point the components at your font:
:root { --chai-type-family-material: "Your Font", system-ui, sans-serif; }Dark theme
The components have a dark theme. It's opt-in: set data-chai-theme="dark"
on <html>, or on any element, and every component inside it switches.
<html data-chai-theme="dark">Chai doesn't follow the system setting on its own, because your app decides
its theme. Following it, overriding a single color, and the dark values
themselves are in the
@chai-ui/tokens README.
Quick start
<!-- App.vue -->
<template>
<ChaiProvider><Create /></ChaiProvider>
</template>
<!-- Create.vue -->
<script setup lang="ts">
import { computed, ref } from "vue";
import { Composer, ResultCard, createFalEngine, useComposer } from "@chai-ui/vue";
const engine = createFalEngine();
const value = ref("");
const { run, submit, cancel, enhance, enhancing } = useComposer({ engine });
const busy = computed(() => run.value?.results.some((r) => r.status === "queued" || r.status === "running") ?? false);
async function onEnhance(prompt: string) {
value.value = await enhance.value!(prompt);
}
</script>
<template>
<Composer v-model="value" :submitting="busy" :enhancing="enhancing" @submit="submit" @abort="cancel" @enhance="onEnhance" />
<ResultCard v-if="run" :results="run.results" :prompt="run.request.prompt" @action="() => {}" />
</template>To try it before setting up a provider, leave out engine and
ChaiProvider: everything then runs on built-in mocks, with no network
calls and no keys.
The Vue names
The props are React's, with Vue's conventions:
| React | Vue |
|---|---|
| value + onChange | v-model |
| x + onXChange | v-model:x, e.g. v-model:attachments, v-model:model-id, v-model:regions |
| onSubmit, onAction, onEnhance, onAbort | @submit, @action, @enhance, @abort |
| useComposer / useMediaAnalyzer hooks | composables returning refs; pass a getter for options that change |
| <EditCard {...edit} /> | edit's fields one by one, with @update:active-version and @update:regions |
| SidePanel's open + onOpenChange, logo and footer | v-model:open, and the #logo and #footer slots (footer also takes text) |
| A Composer chip or menu item's icon | A component |
Some controls show only with a listener, as in React: the optimize button
with @enhance, stop with @abort, the Model menu with @update:model-id.
Docked the bar? Set menu-placement="above" for a bar fixed to the bottom
of the screen, or "below" for one fixed to the top, so every menu opens
into the page. Left out, it's "auto": each menu goes where it fits.
What's in the package
Composer: prompt, attachments, use cases, model and aspect-ratio menus with suggested models, prompt optimization, auto-select routing, stop and retry.MediaAnalyzer+useMediaAnalyzer: media in, a generation-ready prompt out.ResultCard: one prompt's results, paged, with votes, retry, download, share, details, streaming text and quality-check tags (from your backend, or the opt-in judge:useComposer({ judgeModel, judgeChecks })).- Analytics:
@eventon every component reports what the person does and sees, oneChaiEventeach, for any analytics tool:@event="(e) => posthog.capture(e.name, e.properties)". EditCard: marked regions with their own instructions, and each edit as a new version.SidePanel: a panel beside your page with a Composer and its answers, for questions about the page (opt-inpage-context, shown as a removable chip) or anything else, with prompt tips, push or overlay, and either side. Same props as React's, withv-model:open;#logoand#footerslots. See the React README's "A side panel" for the details.useComposer,ChaiProvider, and theToggle,SearchMenuandPaginationbuilding blocks.- The engines and types from
@chai-ui/core, re-exported.
Your API keys stay on your server
The engines call one route on your server, which adds the key. In Nuxt:
// server/api/chai/[...path].ts
import { createChaiHandler } from "@chai-ui/core/server";
const chai = createChaiHandler();
export default defineEventHandler((event) => chai.handle(toWebRequest(event)));Then set FAL_KEY, OPENROUTER_API_KEY or AI_GATEWAY_API_KEY on your server. Other servers,
and authorize and allowedModels for production, are in the
@chai-ui/core README.
Without a server route
In a desktop app (Tauri, Electron) with no server route, write an engine
that hands each run to your app's backend. Pass it to
useComposer({ engine: desktopEngine }) and to
<ChaiProvider :reasoning-engine="desktopEngine">. The
@chai-ui/core README
has a Tauri example, voice input, and what Stop does there.
