PHP port of charmbracelet/bubbles β
15 pre-built TUI components for SugarCraft, including the interactive
Tree (mirrors upstream Bubbles #233), dynamic-height TextArea
(mirrors #910), and per-cell Table::styleFunc(...) (mirrors #246).
composer require sugarcraft/sugar-bits
TextInput,TextArea, andHelpexpose short-form aliases for the most-used setters:placeholder/charLimit/width/height/prompt/validator/styles/separator/ellipsis. The upstream-mirroringwith*long forms still work side-by-side.
Upstream Bubbles ships 13 components; SugarBits ships those 13 plus
AnimatedProgress (the spring-physics variant lives in its own class
to keep the static Progress lean).
| Component | What it does | Notable msgs |
|---|---|---|
Cursor\Cursor |
Animated text cursor | BlinkMsg |
Help\Help |
Render short / full key-help footer from a KeyMap; Help::updateWithBinding($msg, $toggle) flips show-all in response to a key |
β |
Key\Binding |
One key + label + help row; Binding::new(...), Binding::withDisabled(...) factories |
β |
Spinner\Spinner |
Animated loading glyph β 12 built-in styles | Spinner\TickMsg |
Progress\Progress |
Static progress bar (gradient fill optional, withColors(...) / withColorFunc(...)) |
β |
Progress\AnimatedProgress |
Spring-physics-animated progress bar (HoneyBounce-driven) | SpringTickMsg |
Timer\Timer |
Countdown timer; interval(), timeout(), withInterval(float) |
Timer\TickMsg, TimeoutMsg |
Stopwatch\Stopwatch |
Elapsed-time counter; interval(), withInterval(float) |
Stopwatch\TickMsg |
TextInput\TextInput |
Single-line input with autocomplete + validators + Styles |
β |
TextArea\TextArea |
Multi-line editor with line numbers / set-prompt-func / focused() / cursor() / line() / column(); Ctrl+O opens the buffer in $EDITOR (withEditorExtension('.md') to control the syntax-highlight suffix) |
TextArea\TextAreaEditedMsg |
Viewport\Viewport |
Scrollable text region with mouse-wheel, scrollbar, horizontal scroll, setWidth(int) / setHeight(int) |
β |
Paginator\Paginator |
Dot / arabic page indicator | β |
ItemList\ItemList |
Selectable / scrollable / filterable list with status messages | β |
Tree\Tree |
Interactive tree β cursor, expand/collapse, viewport scroll. Mirrors upstream Bubbles #233. | β |
Table\Table |
Selectable data table with Column struct + nav |
β |
FilePicker\FilePicker |
Directory browser with icons / size / sort modes | β |
Forward these into your model's update() so the embedded component
can react: BlinkMsg (Cursor / TextInput), Spinner\TickMsg
(Spinner), Timer\TickMsg + Timer\TimeoutMsg, Stopwatch\TickMsg,
SpringTickMsg (AnimatedProgress), StartStopMsg (Timer / Stopwatch),
TextArea\TextAreaEditedMsg (TextArea's Ctrl+O round-trip).
Each component's update() filters by its own id() so multiple
instances of the same component coexist on one event loop.
use SugarCraft\Bits\TextInput\TextInput;
use SugarCraft\Core\{Cmd, Model, Msg, Program};
use SugarCraft\Core\Msg\KeyMsg;
use SugarCraft\Core\KeyType;
final class Search implements Model
{
public function __construct(public readonly TextInput $ti) {}
public function init(): ?\Closure { return null; }
public function update(Msg $msg): array
{
if ($msg instanceof KeyMsg && $msg->type === KeyType::Enter) {
return [$this, Cmd::quit()];
}
if ($msg instanceof KeyMsg && $msg->type === KeyType::Tab) {
return [new self($this->ti->acceptSuggestion()), null];
}
[$ti, $cmd] = $this->ti->update($msg);
return [new self($ti), $cmd];
}
public function view(): string
{
$body = $this->ti->view();
if (($s = $this->ti->currentSuggestion()) !== null) {
$body .= "\n β $s";
}
return $body;
}
}
[$ti, $cmd] = TextInput::new()
->withSuggestions(['apple', 'apricot', 'banana', 'cherry'])
->showSuggestions()
->withValidator(fn(string $v) => strlen($v) >= 2 ? null : 'too short')
->focus();
(new Program(new Search($ti)))->run();use SugarCraft\Bits\Progress\AnimatedProgress;
$bar = AnimatedProgress::new()
->withWidth(40)
->withDefaultGradient();
[$bar, $cmd] = $bar->setPercent(0.75);
// dispatch $cmd via the Program β ticks re-fire from inside update()
// until the bar settles within 5e-4 of the target.cd sugar-bits && composer install && vendor/bin/phpunit











