drevops/tui is a PHP engine for panel-based terminal forms: keyboard-driven questionnaires that collect a set of answers and hand them back to the caller as typed values.
- Declarative form model. A form is declared with a fluent builder (
Form/PanelBuilder/FieldBuilder): panels of typed fields, each field a widget with its own options, conditions, derivation rules and behaviour. - Two collection modes, one declaration. The same form runs as a full-screen interactive TUI on a terminal, or resolves non-interactively from a JSON payload, per-field environment variables, discovery rules and defaults.
- Application-agnostic. The engine knows nothing about the application it serves; questions and handlers live in the consumer, and applying the collected answers is the consumer's job. It collects; you apply.
- Dependency-light. The runtime dependency surface is a single string-transform package.
The border above is a display option. The same form at the default borderless look, normal spacing:
Full documentation lives at phptui.dev. The in-development build, rebuilt from main ahead of each release, is previewed at tui-docs.netlify.app.
Every feature has a reference page and a runnable, self-contained example in playground/:
| Feature | Summary | Docs | Example |
|---|---|---|---|
| π§ Full-screen TUI | Scrollable panel browser: hubs drill into sub-panels to any depth, contextual key-hint footer, ? help overlay |
panels | 03-panels |
| πͺ Modal panels | A panel marked ->modal() opens as a centered dialog over its dimmed parent, with its own submit/cancel buttons |
panels | 03-panels |
| β‘ Inline editing | A field's editor opens in place on the panel row; ->standalone() opts a field out to full-screen |
panels | 04-inline-editing |
| π§© Widgets | 13 field types: text, number, calendar, textarea, password, select, reorder, suggest, search, file picker, confirm, toggle, pause | widgets | 02-widgets |
| ποΈ Builder-driven | The form is declared in PHP with a fluent builder; the common cases need no code | configuration | 01-quickstart |
| ποΈ Interactive or unattended | run() picks the mode: keyboard on a terminal, otherwise JSON payload + TUI_<ID> environment variables |
headless collection | 05-headless |
| π Derived values | Fields computed from other answers via {{field}} templates and str2name transforms, settling to a fixpoint |
configuration | 06-form-logic |
| π Conditional fields | ->when() conditions (eq/ne/in/contains, composable with all/any/not) drive visibility; form-level fix-ups reconcile answers |
configuration | 06-form-logic |
| βοΈ Declared behaviour | Dynamic defaults, validation and transforms as field closures, or as per-field handler classes resolved by naming convention | field behaviour | 07-field-behaviour |
| π Discovery | Update mode detects defaults from an existing directory: dotenv keys, JSON dot-paths, path checks, directory scans | discovery | 08-discovery |
| π¦ Self-describing answers | Answers carry provenance; toSummary() renders a badged, panel-grouped report and toJson() the machine result; schema(), validate() and agentHelp() describe the form itself |
self-describing answers | 05-headless |
| π¨ Themes | Six built-ins selected by name; a custom theme is a DefaultTheme subclass overriding palette atoms and render methods |
themes | 09-themes |
| β¨οΈ Key bindings | Presets (default, vim, or a class) plus per-binding overrides scoped to navigation or a widget type; conflicts throw at setup |
key bindings | 10-key-bindings |
| β¨ Display modes | Dark/light follows the terminal background, glyphs follow the locale, colour honours NO_COLOR; all three can be forced |
display modes | 11-display-modes |
| π§ͺ Test harness | TuiTester drives the real panel loop from scripted keystrokes, no TTY; assert on answers, output and rendered frames |
testing | 12-testing |
| π Translations | A Translator with per-language catalog files localizes chrome and questions, falling back to English |
translations | 13-translations |
composer require drevops/tuiDeclare a form with the Form builder, then drive it through the Tui facade - the one class that wires the engine, resolver, schema tools and TUI:
use DrevOps\Tui\Builder\Form;
use DrevOps\Tui\Builder\PanelBuilder;
use DrevOps\Tui\Tui;
$form = Form::create('My form')
->panel('general', 'General', fn(PanelBuilder $p) => $p->text('name', 'Your name')->required());
$tui = new Tui($form, handler_namespaces: ['App\\Handler']);
$answers = $tui->run();The facade's surface:
| Call | Purpose |
|---|---|
run($prompts, $version, $directory, $interactive) |
Collect answers; interactive on a TTY, headless otherwise (or forced via $interactive) |
collect($prompts, $directory, $update, $version) |
Headless collection from JSON + environment; $update enables discovery |
interact() |
The interactive panel TUI, explicitly |
schema() / validate($answers) / agentHelp() |
Describe the form as a JSON schema, validate a payload against it, emit agent-facing instructions |
theme($name, $options) / keys($preset, $overrides) |
Select the theme and key bindings |
color($bool) / unicode($bool) / footer($bool) / clearOnExit($bool) / translator($t) |
Display and runtime switches |
form() / engine() / registry() |
The internals, for finer control |
Read the full guide at phptui.dev, and browse playground/ for complete, runnable examples - one directory per feature in the table above.
There's a widget for most things you'd want to ask: text entry, numbers and dates, single and multiple choice, fuzzy search, filesystem browsing, and simple gates. Each one links to its full reference on phptui.dev, and every card below plays back the real interaction in whichever colour scheme - light or dark - your reader is using.
| Calendar A month calendar returning a normalized ISO YYYY-MM-DD; arrows move by day and week. |
|
| Confirm Yes/No toggle; arrows or Space switch, y/n set the choice directly, Enter accepts. |
|
| File picker Browse the filesystem for a path; arrows move, β enters a directory and β returns to its parent. Add ->multiple() for several paths. |
|
| Number Integer entry (digits with an optional leading minus) accepted as an int, with optional min, max and step. |
|
| Password Text rendered as a mask in the editor, the field row and the summary; the accepted value stays plain for the consumer, and can be made revealable. |
|
| Pause An acknowledgement gate; Enter or Space accepts. Unattended runs auto-acknowledge it, so it never blocks automation. |
|
| Reorder Rank a list by moving items into the order you want; Space picks an item up, arrows carry it through the list, Enter accepts. |
|
| Search Single choice with a visible filter line; typing fuzzy-matches and ranks the labels, exact and prefix matches leading. |
|
| Select Single choice from a list; arrows move, Enter accepts the highlighted option, long lists page around the cursor. |
|
| Suggest Free text with autocomplete over a fixed option set: as you type, suggestions are fuzzy-matched and ranked by relevance. |
|
| Text Single-line input with a movable caret; type to insert, arrows move, Backspace deletes, Enter accepts. |
|
| Textarea Multi-line input; Enter inserts a newline, arrows move between lines, Tab accepts, with an external-editor handoff. |
|
| Toggle An inline switch between two labelled values; arrows or Space flip, the first letter of each label sets it directly. |
Six themes ship built-in, selected by name on the Tui facade. Dark or light is a separate mode display option auto-detected from the terminal background, so every adaptive theme serves both:
$tui = (new Tui($form))->theme('midnight');| Name | Palette |
|---|---|
default |
Cyan accents on an auto-detected dark or light base - the out-of-the-box look. |
midnight |
Violet accents, green values, pink highlights. |
frost |
Arctic frost-blue accents, sage values, sand highlights. |
ember |
Burnt-orange accents, olive values, gold highlights. |
mono |
Hue-free - bold weight, grey levels and reverse video for maximum compatibility. |
dos |
Retro MS-DOS: the bright white/cyan/yellow CGA palette in a double-line window, made for a blue terminal background. |
Each renders across every widget and degrades to plain text without ANSI. Here the dark palette (left) and the light palette (right); the themes docs also show every theme inside the rounded border frame:
midnight
frost
ember
mono
dos - the CGA blue screen, painted regardless of the terminal background
Write your own by subclassing DefaultTheme and overriding just its palette - see the theming guide and the playground's OceanTheme.
composer install
composer lint
composer testSee the Contributing guide for the development workflow, quality gates and how the documentation and SVG assets are built.
This repository was created using the Scaffold project template