Getting Started
Requirements
- PHP 8.2+
- Linux, macOS, or WSL — native Windows terminals are not yet supported
Installation
Install Phui with Composer:
composer require samuelweekes/phuiCore concepts
Phui is built from a small set of ideas. Everything else is detail on one of them:
- Components are what you write — the stateful, reactive units of a UI.
- Nodes are what a component's
build()returns — the building blocks on screen. - Events are how the user's input reaches your code.
- The Screen is what you mount your app to, and what runs it.
Components
A component is a class that extends Phui\Component. It's what you actually hand Phui — you never pass a bare node tree, you pass components. Its one required method, build(), returns the node tree to render:
use Phui\Component;
use Phui\Nodes\Node;
use Phui\Nodes\Text;
class Greeting extends Component
{
public function build(): Node
{
return Text::make('Hello, world!');
}
}Components compose: any component can mount another inside its tree with Greeting::mount(), so an app grows as a tree of small components, each owning its own state and behaviour.
Nodes
Nodes are what build() returns — each describes one piece of UI. Three builders cover the basics:
| Builder | Produces |
|---|---|
Text::make(...) | A leaf that renders text. |
Container::make(...) | A box that lays out children on an axis. |
Canvas::make($width, $height) | A fixed grid for drawing custom content. |
Nodes are descriptions, not terminal output — fluent objects carrying structure, layout and style. Containers nest through their children, which is how a UI becomes a tree:
use Phui\Nodes\Container;
Container::make([
Text::make('Hello, world!'),
])
->border()
->padding(1);Phui turns your node tree into live elements, lays them out, and paints only the cells that changed since the last frame.
Phui uses a reconciler, so same-type nodes that change position — in a list, or shown conditionally — need a key to keep their state attached as things move. In practice you rarely manage keys by hand: Node::each() and Node::when() build lists and conditionals with the keys handled for you. See dynamic content and lists for how.
Events
Everything the user does — a key, a click, a scroll, a paste — reaches your app as an event, and you declare handlers for the ones you care about. Keyboard events go to the focused component, declared in bindings(); mouse events go to the node under the cursor:
use Phui\Attributes\State;
use Phui\Bindings\Binding;
use Phui\Events\Keyboard\Keypress;
#[State]
private string $name = 'world';
public function bindings(): array
{
return [
Binding::keypress(
Keypress::ENTER,
fn () => $this->rename('keyboard'),
),
];
}This is also where reactivity comes in. $name is marked #[State], which tells Phui to watch it: change a #[State] property inside a handler and Phui calls build() again, reconciling the new tree against the old. That's the whole loop — build() describes, events change state, and state rebuilds.
The Screen
It all comes together in the Screen: you mount your root component to it, and it does the rest.
use Phui\Screen;
Screen::mount(Greeting::class)->run();run() sets up the terminal, drives the render loop — rebuilding what changed, reconciling, laying out, painting — and restores the terminal cleanly on exit.
Next steps
That's the whole mental model. To put it to work, step through the tutorial — it builds a real file browser from scratch.