Skip to main content

Terminal user interfaces for PHP

A dependency-light PHP engine for building terminal user interfaces: interactive, keyboard-driven forms that collect answers and hand them to your code. Describe the questions in PHP with a fluent builder, add a handler class wherever a question needs real behaviour, and the engine renders a scrollable, themeable TUI - or collects the answers non-interactively from a JSON payload.

The engine knows nothing about the application it serves: it stays generic, the application-specific questions and handlers live in the consumer, and applying the collected answers is the consumer's job, not the TUI's.

Produce box TUIProduce box TUI

Featuresโ€‹

  • ๐Ÿงญ Full-screen TUI - a scrollable, keyboard-driven form: fields group into sections that drill in to any depth (or open as modal dialogs), with a contextual key-hint footer and a ? help overlay
  • โšก Inline editing - a field's editor opens in place on the panel row; opt a field out to full-screen with ->standalone()
  • ๐Ÿงฉ Widgets - calendar, confirm, filepicker, number, password, pause, reorder, search, select, suggest, text, textarea, toggle
  • ๐Ÿ—๏ธ Builder-driven - the form is declared in PHP with a fluent builder; the common cases need no code
  • ๐ŸŽ›๏ธ Interactive or unattended - answer the form by keyboard, or supply the answers up front as a JSON payload and environment variables so it runs without prompting (and emit a JSON schema for agents and forms)
  • ๐Ÿ”— Derived values - compute one field from others with str2name transforms; chains settle to a fixpoint
  • ๐Ÿ”€ Conditional fields - show or hide fields with when rules; a fix-up pass reconciles dependent answers
  • ๐Ÿ” Discovery - detect sensible defaults from the target directory (.env keys, JSON paths, path existence, directory scans)
  • โš™๏ธ Declared behaviour - validation, transforms and dynamic defaults as closures on the field; per-field handler classes remain as a fallback
  • ๐Ÿ“ฆ Self-describing answers - each answer carries a snapshot of its question and its provenance; summaries need no form config
  • ๐ŸŽจ Themes - the whole visual representation (colours, glyphs, layout) is a theme class; ships with dark and light
  • โŒจ๏ธ Key bindings - remap navigation, edit, accept and cancel keys per widget type; ships a vim-style preset, and a bad binding fails loudly at build time
  • โœจ Unicode and ASCII - glyphs follow the terminal locale and colour honours NO_COLOR; both can be forced on the form
  • ๐Ÿงช Test harness - drive the real TUI from scripted keystrokes and assert on the answers and rendered output, no TTY needed
  • ๐ŸŒ Translations - present chrome and questions in another language through a consumer catalog, falling back to English

Quick startโ€‹

Declare a form with the fluent Form builder - a panel of fields, each one a widget - then drive it through the Tui facade, the one class that wires the engine, resolver, schema tools and TUI for you:

use DrevOps\Tui\Builder\Form;
use DrevOps\Tui\Builder\PanelBuilder;
use DrevOps\Tui\Tui;

$form = Form::create('Quick start')
->panel('order', 'New order', function (PanelBuilder $p): void {
// A required single-line text field.
$p->text('name', 'Order name')->required();

// A single choice, starting on "Banana".
$p->select('fruit', 'Fruit')->default('banana')->options([
'apple' => 'Apple',
'banana' => 'Banana',
'cherry' => 'Cherry',
]);

// A multi-select, with one option pre-checked.
$p->select('veg', 'Vegetables')->multiple()->default(['carrot'])->options([
'carrot' => 'Carrot',
'tomato' => 'Tomato',
'spinach' => 'Spinach',
]);

// An integer bounded to a sensible quantity.
$p->number('quantity', 'Quantity')->min(1)->max(99)->default(6);

// A yes/no gate.
$p->confirm('organic', 'Organic only?')->default(FALSE);
});

$tui = new Tui($form);

// Interactive panel TUI on a terminal, headless otherwise.
$answers = $tui->run();

// Or drive a mode directly:
echo $tui->collect('{"name":"Weekly box"}')->toJson(); // headless: JSON + environment
$answers = $tui->interact(); // interactive panel TUI

Run it on a terminal and the panel opens on the form's fields, ready to fill:

The quick-start form: a 'New order' panel with text, select, multi-select, number and confirm fieldsThe quick-start form: a 'New order' panel with text, select, multi-select, number and confirm fields

run() picks the mode automatically - the interactive panel TUI on a terminal, headless otherwise. The facade also exposes schema(), agentHelp() and validate(), and - for finer control - the internals via form(), engine() and registry().

This is the runnable playground/01-quickstart/ example. See Installation to get started and the playground for more complete examples.