THEME
MOTION
HAPTIC
SOUND

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.