---
title: Terminal
description: A terminal view that displays ANSI and Unicode output.
category: Advanced
status: beta
since: 0.6.3
tags: [terminal, logs, xterm, ansi]
syncStatus: reviewed
verified: false
---

## Installation

```bash
pnpm add @talex-touch/tuffex
```

```ts
import { TxTerminal } from '@talex-touch/tuffex/terminal'
import '@talex-touch/tuffex/base.css'
import '@talex-touch/tuffex/terminal/style.css'
```

## Usage

### Interactive Display
`ready` hands over the instance and `data` emits keyboard input; input is never echoed automatically.
:::TuffDemoWrapper{demo="TerminalTerminalDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import type { TerminalInstance } from '@talex-touch/tuffex/terminal'
  import { ref } from 'vue'

  const lastInput = ref('')
  function ready(terminal: TerminalInstance) {
    void terminal.writeln('\x1B[32mANSI · 中文输出 ✓\x1B[0m')
  }
  </script>

  <template>
    <div style="height: 240px">
      <TxTerminal @ready="ready" @data="lastInput = JSON.stringify($event)" />
    </div>
    <output>{{ lastInput }}</output>
  </template>
---
:::

### Read-only Logs
Pausing belongs to the host: keep collecting records, but stop updating the `lines` passed in.
:::TuffDemoWrapper{demo="TerminalLogsDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const lines = ref(['\x1B[32m[info]\x1B[0m 中文日志 ✓'])
  </script>

  <template>
    <TxButton @click="lines.push('[info] next record')">Append</TxButton>
    <TxButton @click="lines.length = 0">Clear</TxButton>
    <div style="height: 224px">
      <TxTerminal read-only :lines="lines" />
    </div>
  </template>
---
:::

### Best Practices

- Use `lines` for declarative logs and `write()` / `writeln()` for streams; replacing `lines` also wipes imperative output.
- Give the container a definite height; explicit `cols` / `rows` override fitting on their own axis only.
- Keep logs `readOnly`, and don't create a PTY just to display them.
- Handle rejected write promises: unmounting or a failed initialization rejects every pending write.
- Run privileged commands only through a trusted host SDK: on an explicit user action, with `command` and `args` passed separately, and cleaned up on session switch or unmount.

## API Reference

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `readOnly` | `boolean` | `false` | Disables input and `data`, and never autofocuses; selection and copy still work. |
| `autoFocus` | `boolean` | `false` | Focuses an interactive terminal after initialization; ignored when read-only. |
| `autoScroll` | `boolean` | `true` | Scrolls to the bottom after output; when off, keeps the viewport, and input doesn't scroll either. |
| `lines` | `readonly (string \| Uint8Array)[]` | `undefined` | Log records, each followed by CRLF; an unchanged prefix appends, anything else resets and replays. |
| `cols` | `number` | fit / initial `80` | Fixed column count; otherwise derived from the container. |
| `rows` | `number` | fit / initial `24` | Fixed row count; otherwise derived from the container. |
| `fontSize` | `number` | `13` | Font size in px; changes trigger a fit. |
| `fontFamily` | `string` | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` | Font stack; changes trigger a fit. |
| `theme` | `ITheme` | host tokens | Partial xterm theme; overrides only the colors you set. |
| `labels` | `Partial<TerminalLabels>` | `{ ariaLabel: 'Terminal' }` | Accessible name of the terminal region and its input textarea. |

### Events

| Event | Payload | Description |
| --- | --- | --- |
| `data` | `string` | Keyboard, paste, and control-sequence input to forward unchanged; never emitted when read-only. |
| `resize` | `{ cols: number; rows: number }` | The initial size and later changes; forward to the PTY if there is one. |
| `ready` | `TerminalInstance` | The display is initialized; the component ref exposes the same methods. |

### Exposed Methods

| Method | Signature | Description |
| --- | --- | --- |
| `write(data)` | `(string \| Uint8Array) => Promise<void>` | Queues raw output, even before readiness; resolves once parsed and adds no newline. |
| `writeln(data)` | `(string \| Uint8Array) => Promise<void>` | Same queue, with CRLF appended. |
| `clear()` | `() => void` | Queues a scrollback clear; the current prompt line stays. |
| `reset()` | `() => void` | Queues a buffer and parser reset, dropping content and terminal modes. |
| `focus()` | `() => void` | Focuses a ready interactive terminal. |
| `fit()` | `() => TerminalSize \| null` | Fits unpinned axes and returns the size; hidden containers keep their last size; null when unavailable. |
| `getSize()` | `() => TerminalSize \| null` | Current columns and rows; null before initialization or after disposal. |

### Types

`/terminal` exports `TerminalProps`, `TerminalEmits`, `TerminalInstance`, `TerminalSize`, `TerminalData`, `TerminalLabels`, `TxTerminalInstance`, and `TERMINAL_DEFAULT_LABELS`. The component is also exported from `/pro` and the root; `Terminal` is the installable wrapper.

## Electron PTY Integration

For trusted Electron renderers with TuffTransport only. The main process owns the PTY and checks `system.shell`; `createTerminalSdk` subscribes before creating, and an aborted `signal` closes a late session automatically.

```vue
<script setup lang="ts">
import type { TerminalInstance, TerminalSize } from '@talex-touch/tuffex/terminal'
import type { TerminalSessionHandle } from '@talex-touch/utils/transport'
import { createTerminalSdk, useTuffTransport } from '@talex-touch/utils/transport'
import { onBeforeUnmount, ref, shallowRef } from 'vue'

const props = defineProps<{ directory: string }>()
const display = shallowRef<TerminalInstance | null>(null)
const status = ref('')
const sdk = createTerminalSdk(useTuffTransport())
let session: TerminalSessionHandle | null = null
let active: AbortController | null = null

function fail(reason: unknown) {
  status.value = reason instanceof Error ? reason.message : String(reason)
}
function stop() {
  active?.abort()
  active = null
  void session?.close().catch(fail)
  session = null
}
async function start() {
  stop()
  const controller = (active = new AbortController())
  display.value?.reset()
  const size = display.value?.getSize()
  try {
    session = await sdk.create({ command: 'node', args: ['-i'], cwd: props.directory, cols: size?.cols, rows: size?.rows }, {
      signal: controller.signal,
      onData(data) {
        if (active === controller)
          void display.value?.write(data).catch(fail)
      },
      onExit(exit) {
        if (active !== controller)
          return
        session = null
        status.value = `exit=${exit.exitCode}, signal=${exit.signal ?? 'none'}`
      },
    })
  }
  catch (reason) {
    if (active === controller)
      fail(reason)
  }
}
const input = (data: string) => void session?.write(data).catch(fail)
const resize = (size: TerminalSize) => void session?.resize(size.cols, size.rows).catch(fail)
onBeforeUnmount(stop)
</script>

<template>
  <TxButton :disabled="!display" @click="start">Start Node REPL</TxButton>
  <TxButton @click="stop">Close</TxButton>
  <div style="height: 280px">
    <TxTerminal @ready="display = $event" @data="input" @resize="resize" />
  </div>
  <output>{{ status }}</output>
</template>
```

## Overview

- Display only: mounting never creates a shell, starts a process, or requests Electron privileges, and the component imports neither transport nor Electron.
- xterm and the fit addon load dynamically in `onMounted`; server rendering touches no browser globals.
- Output, `reset()`, and `clear()` share one ordered queue; binary data is decoded as UTF-8.
- `lines` is watched deeply, so push, index assignment, replacement, and clearing all work; after mutating bytes inside a `Uint8Array`, trigger one reactive update.
- Read-only mode blocks input at both xterm and the event boundary and stops the cursor blink; switching from interactive to read-only blurs the input.
- Unmount disposes xterm, subscriptions, and observers; pending writes reject, and a late dynamic import can't recreate the terminal.

## Technologies

- Built on `@xterm/xterm ^5.5.0` and `@xterm/addon-fit ^0.10.0` (both MIT); the engine stylesheet ships in `terminal/style.css`.
- Colors come from inherited TuffEx tokens and follow theme and contrast changes; a `ResizeObserver` coalesces fits into one frame.
- Source: `packages/tuffex/packages/components/src/terminal/`; process sessions live in `packages/utils/transport/sdk/domains/terminal.ts`.

<TuffDocSourceLink />

## Use cases

- Browser or Electron log viewers whose host owns pause and history.
- Interactive Electron PTYs whose trusted host owns command execution.
- ANSI / Unicode output previews that need no process privileges.

## Accessibility

- xterm screen-reader support is on; read-only output stays selectable and copyable.
- Leave `autoFocus` off unless the user explicitly opens an interactive terminal; log updates never move focus.
