Skip to content

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-ffi flag
  • 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:

bash
# 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-vue

vue-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 ​

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 ​

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 ​

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):

json
{
  "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 ​

bash
pnpm dev

Your 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:

bash
pnpm build   # bundles to dist/main.js
pnpm start   # runs it

What's next? ​

You have a running app — now learn the pieces:

Released under the MIT License.