elmphant / tui
The Elm Architecture for terminal UIs (TUI) in PHP
Requires
- php: ^8.2
- ext-ctype: *
- ext-mbstring: *
- revolt/event-loop: ^1.0
- symfony/http-client-contracts: ^3.5
- symfony/process: ^6.4.33 || ^7.4.5
Requires (Dev)
- deptrac/deptrac: ^4.7
- ergebnis/composer-normalize: ^2.52
- friendsofphp/php-cs-fixer: ^3.75
- infection/infection: ^0.32.6
- league/commonmark: ^2.9
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.0
- symfony/http-client: ^6.4.50 || ^7.4.23
Suggests
- ext-pcntl: Without it a program runs but cannot see signals: no SIGWINCH resize detection, no graceful quit on SIGINT/SIGTERM, no Ctrl-Z suspend. Cmd::fork() also needs it to keep the screen alive during blocking work
- ext-posix: Needed alongside ext-pcntl for Ctrl-Z suspend and resume
- amphp/http-client: ^5 for Cmd::fiber() work whose HTTP requests suspend the fiber instead of blocking (through Symfony's AmpHttpClient)
- league/commonmark: ^2.9 for Paragraph::fromMarkdown(); earlier 2.x releases carry denial-of-service advisories
- phpstan/phpstan: Required to include rules.neon, which enforces the widget and model invariants in your own app
- symfony/http-client: ^6.4.50 || ^7.4.23 for Cmd::http(); the contracts this package requires are the interfaces alone, so an app that never makes a request installs no client
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-11-10 12:18:39 UTC
README
Elmphant TUI
Terminal user interfaces in PHP, built with The Elm Architecture.
Inspired by Go's Bubbletea and Rust's Ratatui. The name mixes the PHP elephant and Elm, and you say it like "elephant" with an m: ELM-fənt too-ee (/ˈɛlm.fənt ˈtuː.i/).
More recordings — a kanban board, dropdowns, forms, and apps that draw below the prompt — are in the showcase.
- Simple model. Your app is an immutable value. A key press gives you a new value, and the framework draws it.
- Widgets included. Lists, tables, forms, tabs, trees, modals, dropdowns, search fields, progress bars and more, with keyboard and mouse support.
- Text you can copy. Drag over the text in a panel to copy it, even while the app reads the mouse. See Copying text.
- No blocking. Processes, HTTP requests and timers run in the background and report back as messages.
- Full screen or inline. An app can take the whole screen, or ask its questions below the shell prompt and leave the answers in the scrollback. See Drawing inline.
- Easy to test. Press keys and read the screen in a plain PHPUnit test, with no terminal.
PHP 8.2+ · MIT License
Status
0.1.0 is the first release. The runtime and the widgets are covered by tests and run in Linux and macOS terminals. Until 1.0 a minor version may change the API, and each change is listed in the changelog. Apps do not run on Windows outside WSL.
Install
composer require elmphant/tui
You need PHP 8.2+, ext-ctype and ext-mbstring. Apps run in POSIX terminals
(Linux, macOS, WSL). Tests run anywhere.
Until 1.0, a minor version may break the API. Require a minor version, for
example ^0.1, and read the changelog before you update.
Some features need an extra package. You only install it if you use the feature:
composer require symfony/http-client # Cmd::http() composer require league/commonmark # Paragraph::fromMarkdown() composer require amphp/http-client:^5 # HTTP inside Cmd::fiber(), with symfony/http-client
Cmd::fork() keeps the screen moving during blocking work only when PHP has the
pcntl extension. Without it, the work runs in the program and the screen
holds still until it is done.
A counter
<?php declare(strict_types=1); require __DIR__ . '/vendor/autoload.php'; use Elmphant\Tui\Application\{App, AppActions}; use Elmphant\Tui\Runtime\Program; use Elmphant\Tui\Widget\Prose\Text; use Elmphant\Tui\Widget\Widget; final readonly class Counter extends App { public function __construct(private int $count = 0) {} public function view(): Widget { return Text::new("Count: {$this->count} (? lists the keys)"); } protected function actions(): AppActions { return parent::actions() ->with('up', ['k', 'up'], static fn (self $app): self => new self($app->count + 1), 'count up') ->with('down', ['j', 'down'], static fn (self $app): self => new self($app->count - 1), 'count down') ->withQuitKeys('x', 'ctrl+q'); } } Program::run(new Counter());
Save it as counter.php and run php counter.php. Each key is declared once,
with what it does and what it is called. ? opens a list of every key that
works right now, read from those declarations.
How it works
- Something happens: a key, a click, a timer, a finished process. It arrives as a message.
- Your app answers with a new model, and maybe a command — a
description of work to do, such as "run
git status". - The framework draws
view()of the new model and runs the command. When the command finishes, its result is the next message.
update() and view() never do the work themselves. That is why an app is easy
to test: a test checks which command was asked for, and nothing runs.
How it works follows one key press through the framework.
Guides
| Guide | What you learn |
|---|---|
| Showcase | Recordings of real apps: a kanban board, dropdowns, forms, and inline apps |
| Building an app | The App class, its hooks, and how interactive widgets keep their state |
| Widgets | Every built-in widget, plus forms, modals, tables and scrolling |
| Layout | Placing widgets side by side or stacked, and making them reflow |
| Styling | Themes, colours in text, what each widget reads, and styling one part of the screen |
| Keys and focus | Shortcuts, typing into fields, vim mode, the key help, footer hints, and where the keyboard goes |
| Commands and effects | Timers, processes, HTTP, cancelling, and effects of your own |
| Testing | Driving an app or one widget from a test, and checking the screen |
| Running and debugging | Program options, drawing inline, terminals, errors, tracing, debugging, and the PHPStan rules |
| Custom widgets | Writing your own widget, popups, and drawing cells by hand |
| How it works | What the framework does between a key press and the screen, in four diagrams |
License
MIT
