* docs(readme): present both usage modes in Quick Start Quick Start now covers the two ways to use vue-tui: 1. Scaffold a project (the @vue-tui/vite template) — Vue SFCs + terminal HMR dev server + vue-tsc. 2. Use @vue-tui/runtime standalone — no plugin, no build step; components as h() render functions, run with `node app.mjs`. Both snippets are verified to run against the published packages. Folds in the former "Add to an existing project" + "Example" sections and drops the now-stale Example entry from the table of contents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): tighten Quick Start prose Drop the redundant build/type-check sentence from the template section, and trim the standalone section's trailing explainers (the h()-vs-template note and the Node TypeScript-runner paragraph) down to the renderToString one-liner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): drop the renderToString aside from Quick Start It's a separate (non-interactive) capability — tangential to the Quick Start's goal of getting an interactive app running. Belongs in API docs, not here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): reframe standalone mode (decoupled from plugin, not "no build") "no plugin, no build step" was misleading — TS/SFC/JSX all need a compile step (same as Ink's JSX), so "no build" isn't a real differentiator. The actual point is that @vue-tui/runtime is a standalone renderer and the @vue-tui/vite plugin only adds the SFC + HMR dev workflow on top, so the runtime can be used on its own in any existing project. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): mode 1 code-free; mode 2 uses SFC (not h) + JSX note - Scaffold (recommended): drop the component snippet — keep it to the commands. - Standalone: author with an SFC and mount with createApp (not h() render functions), and note JSX via @vitejs/plugin-vue-jsx. Verified an SFC builds and renders with plain @vitejs/plugin-vue (no @vue-tui/vite). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): bullet the standalone tooling notes; clearer plugin line Split the two trailing sentences into bullets, and rewrite the @vue-tui/vite line to be direct: it does the SFC setup for you and adds terminal HMR (option 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): frame @vue-tui/vite around HMR, not "SFC setup" The standalone tooling note implied @vue-tui/vite's job is the SFC compilation. Its value is the terminal HMR dev server; reframe the bullet accordingly (it bundles plugin-vue only as the mechanism that makes terminal HMR work). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): reword the HMR bullet to read naturally Replace the clipped "— that's option 1" with a normal sentence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): drop the awkward "option 1" callback in the standalone note Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): show the concrete [vue(), vueTui()] config in the HMR note Now that @vue-tui/vite no longer bundles plugin-vue, make the standalone-mode HMR bullet concrete: you compose them, compiler first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): "HMR support" wording in the standalone note Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
vue-tui
Public beta — the
@vue-tui/runtimeAPI is stabilizing toward 1.0; dev-mode HMR is still experimental. Bug reports welcome.
The Vue framework for terminal UIs. Build with components, develop with HMR, test with confidence.
@vue-tui/runtime · @vue-tui/components · @vue-tui/vite · @vue-tui/testing
- Vue SFC & JSX — write terminal interfaces with
<template>, TSX, or both - Flexbox layout — powered by Yoga, the same engine behind React Native
- Dev toolkit (experimental) — HMR in the terminal via the
@vue-tui/viteplugin (npm run dev) - Input & focus — keyboard handling, focus management, Tab navigation, Kitty keyboard protocol
- Testing harness — out-of-the-box component-level terminal testing — render, simulate input, assert frames
Flappy Bird — one of the examples included in the repo
Quick Start
There are two ways to use vue-tui — scaffold a full project, or drop the runtime into an existing one.
1. Scaffold a project (recommended)
A ready-to-develop setup: Vue SFCs and a terminal HMR dev server via the @vue-tui/vite plugin.
npx tiged vuejs-ai/vue-tui-starter/vite my-app
cd my-app
npm install
npm run dev # in-process terminal dev server with HMR
Edit src/app.vue and watch the terminal update instantly.
2. Use the runtime standalone
@vue-tui/runtime is a standalone Vue renderer, independent of the @vue-tui/vite plugin. Author components as SFCs and mount them with createApp, using your own build:
<!-- app.vue -->
<script setup lang="ts">
import { shallowRef } from "vue";
import { Box, Text, useInput } from "@vue-tui/runtime";
const count = shallowRef(0);
useInput((input) => {
// "+" is Shift+"=" on most keyboards, so accept the bare "=" too.
if (input === "+" || input === "=") count.value++;
if (input === "-") count.value--;
});
</script>
<template>
<Box>
<Text>Count: </Text>
<Text bold color="green">{{ count }}</Text>
<Text dimColor> (+/= and - to change)</Text>
</Box>
</template>
// main.ts
import { createApp } from "@vue-tui/runtime";
import App from "./app.vue";
createApp(App).mount();
- Compile the SFCs with
@vitejs/plugin-vue, or use JSX with@vitejs/plugin-vue-jsx. - For hot-reload (HMR) support while developing, add the
@vue-tui/viteplugin:plugins: [vue(), vueTui()].
Table of Contents
- Quick Start
- Packages
- Examples
- Components
- High-level Components
- Composables (Hooks)
- Testing
- Development
- Contributing
- Credits
- License
Packages
| Package | Description |
|---|---|
@vue-tui/runtime |
The core framework — Vue 3 renderer for the terminal with components (Box, Text, Static, etc.), composables (useInput, useFocus, useApp, etc.), and yoga-based flexbox layout. API stabilizing. |
@vue-tui/vite |
Vite plugin — add vueTui() to vite.config.ts for an in-process terminal dev server with HMR (npm run dev) plus a production build (vite build). Experimental; may change. |
@vue-tui/testing |
Test harness — render in an isolated fake terminal, simulate input, assert output frame by frame |
@vue-tui/components |
High-level components built on the runtime primitives — currently <Spinner> (animated loading), with more to come. |
Examples
| Example | Description |
|---|---|
basic-template |
Vue SFC with <template> syntax |
basic-jsx |
Same app in TSX |
coding-agent |
AI coding agent with LLM streaming and interactive UI |
flappy-bird |
Physics-based terminal game with reactive state and borders |
Components
| Component | Description |
|---|---|
<Box> |
Flexbox container — direction, wrap, align, justify, gap, padding, margin, borders, background |
<Text> |
Styled text — color, bold, italic, underline, strikethrough, dimColor, wrap/truncate modes |
<Spacer> |
Expands to fill available space (flex-grow: 1) |
<Newline> |
Inserts line breaks (configurable count) |
<Static> |
Renders a list of items once, above the redrawn region |
<Transform> |
Applies a string transform function to each rendered line |
High-level Components
The @vue-tui/components package adds higher-level components composed from the runtime primitives — published separately from the core.
| Component | Description |
|---|---|
<Spinner> |
Animated loading spinner — built-in dots/line presets or custom frames, optional label |
Composables (Hooks)
| Composable | Description |
|---|---|
useInput(handler, opts?) |
Handle keyboard input — receives (input, key) with modifier and arrow key detection |
usePaste(handler, opts?) |
Handle bracketed paste — receives the pasted text as a single event |
useFocus(opts?) |
Component-level focus — returns { isFocused, focus } |
useFocusManager() |
App-level focus control — focusNext(), focusPrevious(), focus(id) |
useApp() |
App lifecycle — { exit(error?), waitUntilRenderFlush() } |
useWindowSize() |
Reactive terminal dimensions — { columns, rows } |
useStdin() |
Access stdin stream and raw mode control |
useStdout() |
Write directly to stdout |
useStderr() |
Write directly to stderr |
useBoxMetrics(ref) |
Measure a <Box> via a template ref — reactive { width, height, left, top, hasMeasured } (or measureElement(el) for a one-off { width, height } read) |
useCursor() |
Control the terminal cursor — setCursorPosition(pos) in output coordinates |
useIsScreenReaderEnabled() |
Whether a screen reader is active — returns a boolean for adapting accessible output |
useAnimation(opts?) |
Frame-based animation driver — reactive { frame, time, delta } + reset() |
Testing
The @vue-tui/testing package renders components in an isolated environment and lets you simulate input and assert visual output:
npm install -D @vue-tui/testing
import { defineComponent, shallowRef } from "vue";
import { expect, test } from "vitest";
import { render } from "@vue-tui/testing";
import { Box, Text, useInput } from "@vue-tui/runtime";
test("counter responds to + and - keys", async () => {
const Counter = defineComponent(() => {
const count = shallowRef(0);
useInput((input) => {
if (input === "+") count.value++;
if (input === "-") count.value--;
});
return () => (
<Box>
<Text>Count: {count.value}</Text>
</Box>
);
});
const { lastFrame, stdin } = await render(Counter);
expect(lastFrame()).toContain("Count: 0");
await stdin.write("+");
expect(lastFrame()).toContain("Count: 1");
await stdin.write("-");
expect(lastFrame()).toContain("Count: 0");
});
Development
Requires pnpm and Node.js 22+.
pnpm install # install dependencies
vp run ready # lint, typecheck, test, and build (the full check)
vp run -r test # run tests across all packages
vp run -r build # build all packages
To run an example with terminal HMR, use vanilla vite@8 (the recommended setup): cd examples/basic-template && npm run dev. See that example's README.md for the in-monorepo caveat.
Contributing
Contributions welcome! vue-tui is evolving fast — please open an issue before starting large changes. If you use AI tools, disclose it in your PR and make sure you've reviewed and tested everything before submitting.
Credits
vue-tui is built on the ideas pioneered by Ink — component model, yoga-based layout, focus system, and rendering pipeline — adapted to Vue's philosophy. Thanks to Vadim Demedes, Sindre Sorhus, and the Ink contributors.
License
MIT