Skip to content

Getting Started

Requirements

  • PHP 8.2+
  • Linux, macOS, or WSL — native Windows terminals are not yet supported

Installation

Install Phui with Composer:

bash
composer require samuelweekes/phui

Core 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:

php
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:

BuilderProduces
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:

php
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:

php
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.

php
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.

Released under the MIT License.