Getting Started
This guide walks you through setting up a Vue TermUI project from scratch. It takes a couple of minutes and you'll end up with a live-reloading terminal app.
Prerequisites
Vue TermUI renders through OpenTUI, whose native renderer is loaded over Node's FFI.
- Node.js ≥ 26.3.0, run with the
--experimental-ffiflag - A terminal (most modern terminals work great)
Why --experimental-ffi?
Creating the renderer calls into OpenTUI's native module through Node's foreign-function interface, which is still behind a flag. You only need it at runtime — the dev and start scripts below set it for you.
Installation
Install Vue TermUI and its peers with your package manager of choice:
# pnpm
pnpm add vue-termui vue @opentui/core
pnpm add -D vite @vitejs/plugin-vue
# npm
npm i vue-termui vue @opentui/core
npm i -D vite @vitejs/plugin-vuevue-termui ships its own Vite plugin (compiling .vue files and launching the app), built on top of @vitejs/plugin-vue. @opentui/core is the native renderer: it stays out of the bundle and is loaded from node_modules when your built app runs, so it must be a direct dependency.
Project setup
A minimal project has three files: a Vite config, an entry, and a root component.
vite.config.ts
import { defineConfig } from 'vite'
import vueTermui from 'vue-termui/vite'
export default defineConfig({
plugins: [vueTermui()],
})The plugin compiles your SFCs, registers the terminal host tags, and — in dev — launches your app in-process with HMR, so running vite opens your terminal app directly.
src/App.vue
<script setup lang="ts">
import { Box, Text, onKeyDown, useExit, ref } from 'vue-termui'
const count = ref(0)
const exit = useExit()
onKeyDown((key) => {
if (key.name === 'up') count.value++
if (key.name === 'down') count.value--
if (key.name === 'q') exit()
})
</script>
<template>
<Box border borderStyle="rounded" :padding="1" flexDirection="column" :gap="1">
<Text bold fg="#42b883">Hello from Vue TermUI 👋</Text>
<Text>Count: {{ count }}</Text>
<Text fg="#888888">↑/↓ to change · q or Ctrl+C to quit</Text>
</Box>
</template>src/main.ts
import { createApp } from 'vue-termui'
import App from './App.vue'
// createApp is async — it spins up the OpenTUI renderer first.
const app = await createApp(App)
app.mount()
// Keep the process alive until the user quits.
await app.waitUntilExit()Scripts
Add the run scripts to your package.json. Both pass the FFI flag for you (and silence Node's ExperimentalWarning, which would otherwise pollute the terminal output):
{
"type": "module",
"scripts": {
"dev": "NODE_OPTIONS='--experimental-ffi --disable-warning=ExperimentalWarning' vite",
"build": "vite build",
"start": "node --experimental-ffi --disable-warning=ExperimentalWarning dist/main.js"
}
}ESM only
Vue TermUI is ESM-only. Make sure your package.json has "type": "module".
Run it
pnpm devYour app takes over the terminal. Edit App.vue and watch it hot-reload without losing state — just like a web app. Press q or Ctrl+C to quit.
To ship a single self-contained file:
pnpm build # bundles to dist/main.js
pnpm start # runs itWhat's next?
You have a running app — now learn the pieces:
- Creating an Application — the app lifecycle in depth
- Layout & Boxes — arrange your UI with flexbox
- Styling Text — colors and text attributes
- Handling Input — keyboard and mouse