Public beta.
0.3.0is the first coordinated release of@vue-tui/runtime,@vue-tui/components,@vue-tui/use,@vue-tui/testing, and@vue-tui/vite. The Runtime API is stabilizing toward 1.0; dev-mode HMR remains experimental.
Try it
pnpm dlx tiged vuejs-ai/vue-tui/templates/vite my-app
cd my-app
pnpm install
pnpm dev
pnpm build
node dist/main.mjsDevelopment runs through Vite with terminal HMR. Production builds one self-contained Node ESM file.
Tagged input and explicit rendering
<script setup lang="ts">
import { shallowRef } from "vue";
import { Box, Text, useInput } from "@vue-tui/runtime";
const count = shallowRef(0);
useInput((event) => {
if (event.type === "key") {
if (event.key.name === "up") count.value++;
if (event.key.name === "down") count.value--;
}
});
</script>
<template>
<Box>
<Text>Count: </Text>
<Text bold color="green">{{ count }}</Text>
</Box>
</template>import { createApp } from "@vue-tui/runtime";
import App from "./app.vue";
createApp(App).mount({
mode: "fullscreen",
color: true,
exitOnCtrlC: true,
});useInput() now delivers one frozen text, key, or paste event. Runtime also adds explicit color control, host-based v-show, text alignment, terminal-default colors, and expanded Box and border styling.
Components
@vue-tui/components adds Table and ScrollBox; Newline and Spacer move here from Runtime.
<script setup lang="ts">
import { Table, type TableColumn } from "@vue-tui/components";
interface Process {
pid: number;
name: string;
}
const rows: Process[] = [
{ pid: 1042, name: "vite" },
{ pid: 1088, name: "node" },
];
const columns = [
{ key: "pid", label: "PID", align: "right" },
{
key: "name",
label: "Command",
headerStyle: { bold: true },
cellStyle: (_value, row) => ({
color: row.pid === 1042 ? "green" : "yellow",
}),
},
] satisfies readonly TableColumn<Process>[];
</script>
<template>
<Table :data="rows" :columns="columns" />
</template>Table supports terminal display width, multiline cells, wrapping, alignment, formatting, and structured text styles. ScrollBox provides an application-controlled bounded viewport.
@vue-tui/use is published for the first time with useInputWhileMounted() and <UseInputWhileMounted>.
Terminal testing
import { defineComponent } from "vue";
import { expect, test } from "vitest";
import { Text } from "@vue-tui/runtime";
import { render } from "@vue-tui/testing";
const App = defineComponent(() => () => <Text>Ready</Text>);
test("renders on a fullscreen terminal", async () => {
const result = await render(App, {
mode: "fullscreen",
columns: 80,
rows: 24,
});
try {
expect(result.lastFrame()).toBe("Ready");
expect((await result.screen()).activeBuffer).toBe("alternate");
} finally {
result.dispose();
}
});lastFrame() exposes renderer content. screen() exposes the terminal-emulated cell surface after cursor, screen, stdout, and stderr behavior is applied.
HMR in development, one file in production
// vite.config.ts
import { defineConfig } from "vite";
import vue from "unplugin-vue/vite";
import { vueTui } from "@vue-tui/vite";
export default defineConfig({
plugins: [vue(), vueTui()],
});// tsdown.config.ts
import { defineConfig } from "tsdown";
import vue from "unplugin-vue/rolldown";
export default defineConfig({
entry: ["src/main.ts"],
platform: "node",
format: "esm",
deps: { alwaysBundle: [/./], onlyBundle: false },
plugins: [vue()],
});vueTui() is now dev-only. Compile, evaluation, and render failures appear in the terminal and recover after the next valid edit.
SFC projects using @vue-tui/vite must switch to unplugin-vue/vite; its default client transform produces the render functions required by the terminal renderer. JSX projects continue to use @vitejs/plugin-vue-jsx.
SFC production builds use tsdown with unplugin-vue/rolldown; JSX production builds use unplugin-vue-jsx/rolldown.
Upgrade notes
Vue ^3.5.0 is required, and all official vue-tui packages are now version 0.3.0.
- useInput((input, key) => {
- if (key.upArrow) count.value++;
- });
+ useInput((event) => {
+ if (event.type === "key" && event.key.name === "up") {
+ count.value++;
+ }
+ });usePaste() is folded into useInput() as event.type === "paste".
- import { Newline, Spacer, Static } from "@vue-tui/runtime";
+ import { Newline, Spacer } from "@vue-tui/components";
+ import { Static } from "@vue-tui/runtime/inline";Static now uses ordinary Vue iteration:
- <Static :items="completed">
- <template #default="{ item }">
- <Entry :entry="item" />
- </template>
- </Static>
+ <Static v-for="item in completed" :key="item.id">
+ <Entry :entry="item" />
+ </Static>Static is Inline-only and is rejected on a Fullscreen surface.
- renderToString(App, { columns: 80 });
+ renderToString(App, { width: 80, height: Infinity });The old renderer was vertically unbounded. In 0.3, omitting height models a 24-line surface; use Infinity to retain unbounded output.
- createApp(App).mount({ alternateScreen: true });
+ createApp(App).mount({ mode: "fullscreen" });exitOnCtrlCnow defaults tofalse; opt in withexitOnCtrlC: true.useWindowSize()was removed. UseuseLayoutSize()only when root layout bounds—not physical terminal dimensions—are what the application needs.Transformwas removed without replacement. Other published experimental animation, cursor, focus-manager, output-stream, and accessibility APIs were also removed.
Full details: Runtime, Components, Testing, Use, and Vite.