Ghostty's full VT engine, as a Flutter widget

pub package ci

Flutter terminal widget on top of Ghostty's libghostty-vt engine.

Android iOS Linux macOS Web Windows

Overview

  • Adapts to the host: mouse and keyboard on desktop, touch and soft keyboard on mobile, both on web.
  • TerminalController owns the terminal and connects to a backend (PTY, SSH, socket) via output/resize/bell/title callbacks. Helpers for I/O, selection, focus, scrolling, paste, and mode toggling.
  • Drag, double-click, triple-click, and Alt+drag selection over wide characters (CJK, emoji, VS16, combining marks) with cell-snapped boundaries.
  • Built-in copy, paste, select all, and clear shortcuts with platform-aware defaults. Extend or replace with any Flutter Intent.
  • Themes for ANSI 16, 256-color, and truecolor palettes; cursor; hyperlinks; fonts. Immutable and lerp-able.
  • Links for OSC 8 metadata, text URLs, file paths, and custom regex rules with activation callbacks.

Getting started

dependencies:
  flterm: ^0.0.4

On web, initialize the wasm module once before mounting any terminal:

import 'package:flterm/flterm.dart';
import 'package:flutter/foundation.dart';

if (kIsWeb) {
  await initializeForWeb(Uri.parse('assets/libghostty.wasm'));
}

Usage

A TerminalController owns the terminal and talks to your I/O. A TerminalView renders it.

import 'package:flterm/flterm.dart';

final controller = TerminalController()
  ..onOutput = (bytes) => pty.write(bytes)
  ..onResize = (cols, rows) => pty.resize(cols, rows)
  ..onBell = playSound;

ptyOutputStream.listen(controller.write);

TerminalView(
  controller: controller,
  theme: TerminalTheme.dark(),
);

The same controller drives the terminal programmatically:

// I/O
controller.sendText('ls -la\n');

// Selection
controller.selectAll();
print(controller.selectedText());

// Clipboard
controller.paste('hello');

// Reset
controller.clear();

Links are configured on the view. Built-in detection covers OSC 8 metadata, text URLs, and file paths.

TerminalView(
  controller: controller,
  linkSettings: LinkSettings(
    onActivate: (link) {
      if (link.uri case final uri?) openUri(uri);
      if (link.file case final file?) openFile(file);
    },
  ),
);

Custom matchers can add project-specific selectors:

TerminalView(
  controller: controller,
  linkSettings: LinkSettings(
    rules: [
      .regex(
        id: 'issue',
        pattern: RegExp(r'#(\d+)'),
      ),
    ],
    onActivate: (link) {
      if (link.id == 'issue') openIssue(link.text.substring(1));
    },
  ),
);

Note: Idle link styling and custom rules with LinkHighlightMode.always scan visible content after terminal updates. The cost grows with output rate, visible rows, and line length, which can impact rendering performance and frame rate. Avoid always-on or idle highlighting unless that extra scanning is acceptable.

Custom themes are constructed directly:

TerminalView(
  controller: controller,
  theme: TerminalTheme(
    palette: ColorPalette(
      ansiColors: const [/* 16 ANSI colors */],
      background: const Color(0xFF1D1F21),
      foreground: const Color(0xFFC5C8C6),
    ),
    fontFamily: 'JetBrains Mono',
    fontSize: 14,
    cursor: const CursorTheme(shape: CursorShape.bar),
  ),
);

License

MIT. See LICENSE.

Libraries

flterm
Flutter terminal renderer and widget APIs.