tui

js/tty/tui.ts

fino:tty/tui — terminal host renderer for fino:ui components.

This module is the first host for the portable fino:ui core. It provides terminal component primitives, deterministic frame rendering for tests, and a small fullscreen live renderer. The layout engine is intentionally a Fino subset: row/column direction, fixed sizes, flex spacers, gap, padding, borders, simple alignment, wrapping, and clipping.

V1 is terminal-only and POSIX-oriented. It includes raw keyboard input and SGR mouse events for fullscreen apps, but it does not implement DOM/HTML output, React hooks, or inline terminal regions.

/** @jsxImportSource fino:ui *\/
import { Box, Text, renderFrame } from 'fino:tty/tui';

const frame = renderFrame(
  <Box border padding={1}><Text>Hello</Text></Box>,
  { width: 20, height: 3 },
);

Interfaces

interface BoxProps extends Props {

Props accepted by Box.

Properties

width?: number
height?: number
flex?: number
direction?: Direction
gap?: number
padding?: number
paddingX?: number
paddingY?: number
border?: boolean
align?: Align
background?: BackgroundColor
children?: Child

interface TextProps extends Props {

Props accepted by Text.

Properties

wrap?: boolean
align?: Align
background?: BackgroundColor
children?: Child

interface SpacerProps extends Props {

Props accepted by Spacer.

Properties

width?: number
height?: number
flex?: number

interface InputProps extends Props {

Props accepted by Input.

Properties

value?: string
placeholder?: string
focused?: boolean
background?: BackgroundColor

interface ButtonProps extends Props {

Props accepted by Button.

Properties

label?: string
focused?: boolean

interface ListProps extends Props {

Props accepted by List.

Properties

items: string[]
selectedIndex?: number

interface ScrollViewProps extends Props {

Props accepted by ScrollView.

Properties

width?: number
height?: number
offset?: number
children?: Child

interface RenderFrameOptions {

Options for deterministic terminal snapshot rendering.

Properties

width: number
height: number

interface TerminalSize {

Current terminal viewport size in character cells.

Properties

width: number
height: number

interface RenderOptions {

Options for live fullscreen terminal rendering.

Properties

width?: number
height?: number
input?: boolean
mouse?: boolean
onEvent?: (event: TuiEvent, app: TuiApp) => void | Promise<void>

interface TuiApp {

Handle returned by render() for updating or stopping a fullscreen app.

Methods

update(element: VNode): void
stop(): void

Properties

input?: TuiInput

interface TuiKeyEvent {

Keyboard event decoded from terminal input.

Properties

type: 'key'
key: string
text?: string
ctrl?: boolean
alt?: boolean
shift?: boolean

interface TuiMouseEvent {

Mouse event decoded from SGR terminal mouse reporting.

Properties

type: 'mouse'
action: 'press' | 'release' | 'drag' | 'move' | 'wheel'
button: 'left' | 'middle' | 'right' | 'none' | 'wheel-up' | 'wheel-down'
x: number
y: number
ctrl: boolean
alt: boolean
shift: boolean

interface TuiInputOptions {

Options for creating a raw terminal input reader.

Properties

mouse?: boolean

Types

type TuiEvent = TuiKeyEvent | TuiMouseEvent

Terminal input event consumed by TUI applications.

Functions

function Box(props: BoxProps): VNode

Terminal box container with row/column layout, padding, gap, and borders.

function Text(props: TextProps): VNode

Terminal text node with optional wrapping.

function Spacer(props: SpacerProps): VNode

Flexible or fixed empty space inside a Box.

function Input(props: InputProps): VNode

Single-line text input primitive for terminal forms.

function Button(props: ButtonProps): VNode

Push button primitive rendered as bracketed terminal text.

function List(props: ListProps): VNode

Vertical list primitive with a selected row marker.

function ScrollView(props: ScrollViewProps): VNode

Clipped viewport over child content.

function decodeTuiInput(bytes: Uint8Array): TuiEvent[]

Decode one terminal input byte chunk into TUI input events.

The decoder understands printable UTF-8, common control keys, arrow/function CSI sequences, and SGR mouse reporting (CSI < code ; x ; y M/m). SGR mouse coordinates are converted to zero-based x/y values.

function createTuiInput(options: TuiInputOptions = {}): TuiInput

Create a raw terminal input reader for TUI applications.

The reader enables raw mode immediately. Call close() when the application exits so terminal state is restored.

function getTerminalSize(): TerminalSize

Return the current terminal viewport size.

The TUI host asks the terminal with ioctl(TIOCGWINSZ) when possible and falls back to environment dimensions in non-interactive contexts.

async function measureTerminalSize(): Promise<TerminalSize>

Measure the visible terminal viewport with an ANSI cursor-position query.

This is slower than getTerminalSize() but handles terminal panes where ioctl(TIOCGWINSZ) or environment dimensions are stale. The function enters raw mode briefly, moves the cursor to a very large coordinate, asks the terminal to report the clamped cursor position, restores the cursor, and returns the reported row/column. If the terminal does not answer quickly, it falls back to getTerminalSize().

function renderFrame(element: VNode, options: RenderFrameOptions): string

Render an element tree to a deterministic terminal frame.

The returned string contains exactly height lines joined with \n, and each line is padded or clipped to width cells.

function render(element: VNode, options: RenderOptions = {}): TuiApp

Render a fullscreen terminal app and return a lifecycle handle.

This enters the alternate screen, hides the cursor, writes the current frame, and restores terminal state from stop(). Width and height default to the current terminal size when available.

Classes

class TuiInput {

Raw terminal input reader for keyboard and mouse events.

Constructors

constructor(options: TuiInputOptions = {})

Methods

async read(): Promise<TuiEvent | null>

Read the next decoded keyboard or mouse event from stdin.

close(): void

Restore raw mode and mouse reporting.