PHPackages                             sugarcraft/sugar-bits - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. sugarcraft/sugar-bits

ActiveLibrary[CLI &amp; Console](/categories/cli)

sugarcraft/sugar-bits
=====================

PHP port of charmbracelet/bubbles — 15 pre-built TUI components (text input, dynamic-height textarea, list, table with per-cell styleFunc, tree, viewport, file picker, progress, spinner, etc.) for SugarCraft.

11.6k1PHP

Since Jun 3Pushed 1mo agoCompare

[ Source](https://github.com/sugarcraft/sugar-bits)[ Packagist](https://packagist.org/packages/sugarcraft/sugar-bits)[ RSS](/packages/sugarcraft-sugar-bits/feed)WikiDiscussions master Synced 3w ago

READMEChangelogDependenciesVersions (1)Used By (1)

[![sugar-bits](.assets/icon.png)](.assets/icon.png)

SugarBits
=========

[](#sugarbits)

[![CI](https://github.com/detain/sugarcraft/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/detain/sugarcraft/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/45a2c1c468707b343bea1507581e02a2e09005d9610bebf65e8c70ef779ad8f3/68747470733a2f2f636f6465636f762e696f2f67682f64657461696e2f737567617263726166742f6272616e63682f6d61737465722f67726170682f62616467652e7376673f666c61673d73756761722d62697473)](https://app.codecov.io/gh/detain/sugarcraft?flags%5B0%5D=sugar-bits)[![Packagist Version](https://camo.githubusercontent.com/56c3975b2d3efc1ecd50fad39f8beab45c5046d7129467943f53cf66801ea367/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f737567617263726166742f73756761722d626974733f6c6162656c3d7061636b6167697374)](https://packagist.org/packages/sugarcraft/sugar-bits)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP](https://camo.githubusercontent.com/fd3accad83cd317a9843432403191b4e555c55d57d63e10897f3f1dff5ed169e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d254532253839254135382e332d3838393262662e737667)](https://www.php.net/)

[![demo](.vhs/spinners.gif)](.vhs/spinners.gif)

PHP port of [charmbracelet/bubbles](https://github.com/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`, and `Help` expose short-form aliases for the most-used setters: `placeholder` / `charLimit` / `width` / `height` / `prompt` / `validator` / `styles` / `separator` / `ellipsis`. The upstream-mirroring `with*` long forms still work side-by-side.

Components
----------

[](#components)

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

ComponentWhat it doesNotable 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 *(deprecated alias — re-exported from `SugarCraft\Forms\Spinner` in `sugarcraft/candy-forms`)*`Spinner\TickMsg``Progress\Progress`Static progress bar (gradient fill optional, `withColors(...)` / `withColorFunc(...)` / `withShowValue(...)`)—`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 + `ValidateOn` timing + restrict pattern + vim mode + placeholder styling + prefix/suffix + `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 + multi-column sort—`Tabs\Tabs`Tabbed panel — keyboard (`Tab`/`Shift+Tab`/`1-9`) + mouse navigation, wrap/clamp modes, scrollable overflow—`FilePicker\FilePicker`Directory browser with icons / size / sort modes—### Vim mode

[](#vim-mode)

`TextInput` vim mode (Insert/Normal/Visual/VisualLine keybindings) is powered by `candy-forms`'s shared `VimKeyHandler` — the same handler backing `sugar-prompt` and `sugar-readline`. Adding a new binding to the `VimAction` enum benefits all three libs at once. The per-lib opt-in flag `withVimMode(true)` is preserved; consumers control whether vim mode is enabled.

### Msg routing cheat-sheet

[](#msg-routing-cheat-sheet)

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.

Quickstart — TextInput with autocomplete
----------------------------------------

[](#quickstart--textinput-with-autocomplete)

```
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();
```

Quickstart — animated progress bar
----------------------------------

[](#quickstart--animated-progress-bar)

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

Quickstart — TextInput with placeholder styling and prefix/suffix
-----------------------------------------------------------------

[](#quickstart--textinput-with-placeholder-styling-and-prefixsuffix)

```
use SugarCraft\Bits\TextInput\TextInput;
use SugarCraft\Sprinkles\Style;
use SugarCraft\Core\Util\Color;

$ti = TextInput::new()
    ->withPlaceholder('Enter command…')
    ->withPlaceholderStyle(Style::new()->faint())           // default: dim
    ->withPrefix('$ ')                                      // fixed prefix
    ->withSuffix('
