← All 40 libraries

SugarWidgets

🧁 SugarWidgets

Ready-made TUI components — inputs, tables, spinners, and more

inspired by charmbracelet/bubbles components widgets forms input

SugarWidgets code coverage

TextInput, TextArea, ItemList, Table, Tree, Viewport, FilePicker, Progress, Spinner, Cursor, Help, Key, Paginator, Stopwatch, Timer. Everything you'd hand-roll on day one of a TUI project, ready to drop into your Model.

Install

composer require sugarcraft/sugar-widgets

Quickstart

use SugarCraft\Widgets\TextInput\TextInput;
use SugarCraft\Widgets\Spinner\Spinner;
use SugarCraft\Core\{Cmd, Model, Msg, Program, Subscriptions};
use SugarCraft\Core\Msg\KeyMsg;
use SugarCraft\Core\KeyType;

final class Search implements Model {
    public function __construct(
        public readonly TextInput $ti,
        public readonly Spinner $sp,
    ) {}

    public function init(): ?\Closure { return $this->sp->tick(); }
    public function subscriptions(): ?Subscriptions { return null; }

    public function update(Msg $msg): array {
        if ($msg instanceof KeyMsg && $msg->type === KeyType::Enter) {
            return [$this, Cmd::quit()];
        }
        [$ti, $cmd]  = $this->ti->update($msg);
        [$sp, $cmd2] = $this->sp->update($msg);
        return [new self($ti, $sp), Cmd::batch($cmd, $cmd2)];
    }

    public function view(): string {
        return $this->sp->view().' '.$this->ti->view();
    }
}

[$input] = TextInput::new()->focus();   // focus() returns [TextInput, ?Cmd]
(new Program(new Search($input, Spinner::new())))->run();

What's in the box

Spinner12 styles — line, dot, miniDot, points, pulse, globe, meter, jump, moon, monkey, hamburger, ellipsis.
ProgressStatic Progress + spring-physics AnimatedProgress driven by SugarSpring.
TextInput / TextAreaSingle + multi-line. Autocomplete, validators, ValidateOn timing control, keystroke restrict pattern filter, line numbers, soft-wrap, dynamic-height growth.
ItemListSelectable, scrollable, filterable. Status messages, custom delegates.
TableSelectable data table with Column DTOs, header highlight, vertical nav, per-cell styleFunc(row, col) => Style, multi-column sort with withSort() / thenSortBy() / clearSort(), filtering with withFilterable() / withFilter() / withFilterPredicate() (default: case-insensitive substring match on all visible columns), and pagination with withPageSize() / paginator() / withPage() / nextPage() / prevPage() / pageFirst() / pageLast() — integrates with sort and filter.
TreeInteractive tree view — cursor nav, expand / collapse, viewport scroll, g / G jump.
ViewportScrollable text region. Mouse wheel, scrollbar, keyboard nav.
FilePickerDirectory browser with icons, size, sort modes.
TextEdit + OptionRowbtop-derived helpers: Input\TextEdit is an immutable inline editor with an underline caret (no hardware cursor), grapheme-cluster editing, cell-budget windowing and a digits-only mode; Menu\OptionRow::render() draws btop's two-line options-menu row. Progress::withColorFunc closures also receive the bar width for position-coloured ramps.
Paginator + Help + KeyDot / arabic page indicator, key-help footer, KeyMap binding DSL.

TextEdit + OptionRow

use SugarCraft\Widgets\Input\TextEdit;
use SugarCraft\Widgets\Menu\OptionRow;

$edit = TextEdit::new('firefox')->home()->right()->right();
echo $edit->view();      // "fi\e[4mr\e[24mefox" — underline caret on "r"

echo OptionRow::render('Update ms', TextEdit::new('2000', numeric: true),
    selected: true, editing: true, hasArrows: true, editable: true);

Source & demos

Try the quickstart →

API

ClassMethodDescription
TextInputnew()Create a new text input instance
TextInputupdate(Msg)Handle input events
TextInputwithValidateOn(ValidateOn)Set validation timing — None / Blur / Change / Submit
TextInputwithRestrict(string $pattern)PCRE regex filter — only matching keystrokes are accepted
ValidateOnenumCases: None / Blur / Change / Submit — controls when validation fires
TextAreanew()Create a multi-line text area
Spinnernew(Style)Create spinner with given style
Spinnertick()Return tick command for animation
Tablenew(headers, rows)Create data table
TablewithSort(string $column, SortDirection $dir = Asc)Set primary sort column and direction — clears prior sort chain
TablethenSortBy(string $column, SortDirection $dir = Asc)Add secondary (or further) sort tiebreaker
TableclearSort()Remove all sort criteria, restoring insertion order
TablesortState(): SortStateReturn current sort criteria (readonly)
TablewithFilterable(bool)Enable or disable the filter feature
TablewithFilter(string $query)Set filter query string — default: case-insensitive substring match on all visible columns
TablewithFilterPredicate(?Closure(list<string>): bool)Custom filter callable — null restores the default
Tablefilterable(): boolReturn whether filtering is enabled
Tablefilter(): stringReturn the current filter query string
TablefilterPredicate(): ?ClosureReturn the current custom filter predicate
SortDirectionenumCases: Asc / Desc — use toggle() to flip direction
SortStateDTOImmutable list of (column index, SortDirection) pairs
TablewithPageSize(int $size)Set rows per page — 0 disables pagination, ≥1 enables it
TablewithPage(int $page)Navigate to a zero-based page (clamps to valid range)
TablenextPage()Advance one page — returns new Table
TableprevPage()Retreat one page — returns new Table
TablepageFirst()Jump to the first page — returns new Table
TablepageLast()Jump to the last page — returns new Table
TablepageSize(): intReturn rows per page (0 = pagination disabled)
TablecurrentPage(): intReturn the current zero-based page
PaginatortotalPages(): intReturn total page count (via the table's paginator)
Tablepaginator(): PaginatorReturn a Paginator wired to the table's current page state
Treenew(root)Create tree view
Viewportnew(content)Create scrollable viewport
Progressnew(ratio)Create progress bar
ProgresswithColorFunc(?Closure)Per-filled-cell colour fn(int $i, int $filledCells, float $percent, int $barWidth): Color — the 4th arg (true bar width) enables btop position-coloured ramps; 3-arg closures still work
TextEditnew(text, ?caret, numeric)Immutable inline editor (btop Draw::TextEdit); underline caret, grapheme-cluster caret
TextEditleft() / right() / home() / end() / withCaret(int)Caret movement (clamped)
TextEditinsert(string) / backspace() / delete() / clear()Edits; numeric mode rejects non-digit input
TextEditwithNumeric(bool)Digits-only mode — strips existing non-digits
TextEditview(int $limit = 0)Render with SGR 4 underline caret, windowed to $limit display cells
OptionRowrender(name, value, selected, editing, hasArrows, …)btop options-menu two-line row: bold centred name, ← value → / ↵, live TextEdit while editing
FilePickernew(directory)Create file picker
ItemListnew(items)Create selectable item list

Demos.

VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.

Spinner gallery

Spinner gallery

All 12 spinner styles ticking in parallel.
TextInput

TextInput

Single-line input with autocomplete + placeholder.
TextArea

TextArea

Multi-line editor with line numbers + soft-wrap.
ItemList

ItemList

Filterable scrollable list with status messages.
Table

Table

Selectable data table with column delegates + per-cell styleFunc.
Tree

Tree

Interactive tree — cursor, expand / collapse, scroll.
Viewport

Viewport

Scrollable text region with mouse wheel + scrollbar.
FilePicker

FilePicker

Directory browser with icons + size + sort.
Progress

Progress

Spring-physics-animated progress bar.
Cursor

Cursor

Animated text cursor with BlinkMsg tick.
Help

Help

Short / full key-help footer from a KeyMap.
Paginator

Paginator

Dot + arabic page indicator.
Stopwatch

Stopwatch

Elapsed-time counter.
Timer

Timer

Countdown timer.