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.