Files
Yunfei He 0a774ddc8c refactor(vite)!: make @vue-tui/vite dev-only; build apps with tsdown (#247)
`vite build` is a browser-first bundler, so producing a runnable Node program
from it meant constantly overriding its defaults (platform, externalization,
inlineDynamicImports, module-preload). Building a Node app is a Node bundler's
job — so split vue-tui's two jobs across two tools:

- @vue-tui/vite is now DEV-ONLY (in-terminal dev server + HMR). Removed
  buildConfigPlugin and the externalize predicate: deleted src/build.ts,
  src/external.ts (+ external.spec.ts) and test/build.sequential.test.ts.
  vueTui() returns just the dev + dev-vmod plugins, and entry normalization is
  dev-only; dev.spec.ts updated.
- Production builds move to tsdown + unplugin-vue: a plain tsdown.config.ts that
  bundles the whole app into one self-contained Node file (dist/*.mjs) runnable
  with no node_modules. platform:node keeps builtins external and emits a real
  createRequire for CJS deps; deps.alwaysBundle inlines everything else.
- Migrated all five examples to tsdown (including flappy-bird, which hand-rolled
  a vite lib-mode self-contained build), and rewrote the examples smoke suite to
  build via tsdown and launch each self-contained bundle from an empty sandbox.
- Added tsdown + unplugin-vue-jsx to the catalog; updated the root and
  @vue-tui/vite READMEs.

BREAKING CHANGE: @vue-tui/vite no longer configures `vite build`. Build the app
with tsdown instead (see the @vue-tui/vite README, examples/*, and the starter).
2026-07-06 00:29:28 +08:00

14 KiB

vue-tui

Public beta — the @vue-tui/runtime API 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 npm version @vue-tui/components npm version @vue-tui/vite npm version @vue-tui/testing npm version

  • 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/vite plugin (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

Flappy Bird built with vue-tui

Quick Start

There are two ways to use vue-tui — scaffold a full project, or drop the runtime into an existing one.

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();

Table of Contents

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). Dev only; the production build is a plain tsdown config that bundles the app into one self-contained Node file (see the starter and examples/*/tsdown.config.ts). 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
<ScrollBox> Bounded scroll viewport with mouse-wheel scrolling and sticky-bottom behavior
<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
useMouseInput(handler, opts?) Handle terminal mouse input — currently SGR wheel events with ref-counted mouse-mode ownership
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