VUE
@cathode-ui/vue is a full framework-mirror of
@cathode-ui/react — the same 45 primitives, same
tokens, same motion/haptic/sound philosophy, implemented as
idiomatic Vue 3 <script setup> + <template>
SFCs. Works with Vite, Nuxt 3, and any other Vue 3.3+ runtime.
Install
npm install @cathode-ui/vue Peer dep: vue >= 3.3.
Setup
Import the CSS once at the app entry, then use CathodeProvider at the root of your tree.
1. Entry file (main.ts):
import { createApp } from 'vue';
import App from './App.vue';
import '@cathode-ui/vue/tokens.css'; // CSS variables + dark/light
import '@cathode-ui/vue/fonts.css'; // JetBrains Mono (optional)
import '@cathode-ui/vue/styles.css'; // compiled component rules
createApp(App).mount('#app'); 2. Root component (App.vue):
<script setup lang="ts">
import { ref } from 'vue';
import {
CathodeProvider, TerminalFrame, Stack,
Button, Toggle, Counter, DotLeader, PulsingDot,
} from '@cathode-ui/vue';
const armed = ref(false);
const wpm = ref(12);
</script>
<template>
<CathodeProvider theme="dark" motion="strong">
<TerminalFrame title="TELEMETRY" accent="info">
<Stack :gap="8">
<Stack direction="row" :gap="8" align="center">
<PulsingDot color="var(--cathode-color-success)" />
<span>LIVE</span>
</Stack>
<DotLeader label="LATENCY" value="42 MS" />
<Toggle v-model="armed" label="ARMED" accent="danger" />
<Counter v-model="wpm" :min="5" :max="40" label="WPM" />
<Button variant="primary" @click="onSave">SAVE</Button>
</Stack>
</TerminalFrame>
</CathodeProvider>
</template> CathodeProvider accepts theme
(auto / dark / light),
motion (none / subtle /
strong), haptic (bool), sound
(bool, default off), and ai (a
CathodeAIProvider instance). Every prop is optional.
Nuxt 3
Cathode primitives use client-only browser APIs
(navigator.vibrate, Web Audio, portals via
<Teleport>), so wrap Cathode trees in
<ClientOnly> — or render in the
app.vue after mount via onMounted. Import
the three CSS files in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
css: [
'@cathode-ui/vue/tokens.css',
'@cathode-ui/vue/fonts.css',
'@cathode-ui/vue/styles.css',
],
}); Vue vs React API deltas
Same 45 components, same prop names wherever possible — but Vue idioms pull a few surfaces apart from their React twins. If you're porting a Cathode-React app to Vue, these are the differences.
v-model replaces value + onChange
Every controlled input uses v-model instead of the
React value + onChange pair. Applies to
TextField, TextArea, Select,
Checkbox, RadioGroup, Toggle,
Counter, Tabs, Pagination,
Accordion (v-model:expandedIds), and
Popover (v-model:open).
<!-- Vue --> <!-- React -->
<Toggle v-model="armed" /> <Toggle value={armed} onChange={setArmed} />
<Counter v-model="wpm" /> <Counter value={wpm} onChange={setWpm} /> Events replace on* handler props
@click, @select, @remove,
@close, @complete,
@sortChange, @rowClick,
@actionResult, @done.
Slots replace ReactNode props
Where React passed a ReactNode via props, Vue uses
named or default slots.
| Component | React (prop) | Vue (slot) |
|---|---|---|
Button / Pill / StatusTile | icon | #icon |
Menu / Popover | trigger | #trigger |
SearchBar | icon | #icon + showIcon prop |
Accordion | items[].content: ReactNode | default scoped slot or string content |
Table | columns[].render(row, i) | #cell-<key>="{ row, value }" |
clickable prop replaces "listener present"
Card and StatusTile in React switch to
<button> when you pass onClick. Vue
has no prop-introspection equivalent, so both accept an explicit
clickable prop:
<Card clickable @click="open">…</Card>
<StatusTile clickable @click="talk" title="TALK" subtitle="HOLD">
<template #icon><IconBroadcast/></template>
</StatusTile>
Removable Tag
<!-- Vue --> <!-- React -->
<Tag removable @remove="drop">GEODESY</Tag> <Tag onRemove={drop}>GEODESY</Tag>
AI composables replace hooks
useAiSuggest, useAiChat,
useAiAction are Vue composables that return reactive
refs. Pass a ref (or getter) for any reactive source.
import { ref } from 'vue';
import { useAiSuggest, useAiChat } from '@cathode-ui/vue';
const prefix = ref('');
const { suggestion, accept } = useAiSuggest(prefix, { enabled: ref(true) });
const { messages, send, streaming, cancel, reset } = useAiChat();
AI provider
Cathode stays provider-agnostic — apps supply a
CathodeAIProvider (suggest,
chat, act) via CathodeProvider:
import { CathodeProvider } from '@cathode-ui/vue';
import { myAiProvider } from './my-ai';
<CathodeProvider :ai="myAiProvider">
<App />
</CathodeProvider>
See AI providers for the interface
shape — it's identical across the React and Vue packages.
Next: browse components, or jump back to Getting Started.