sugarcraft/sugar-prompt

PHP port of charmbracelet/huh — interactive form library (Note, Input, Confirm, Select, MultiSelect, Text, FilePicker) with multi-page Group support, 6 stock themes, and form-level KeyMap override per binding.

Maintainers

Package info

github.com/sugarcraft/sugar-prompt

Documentation

pkg:composer/sugarcraft/sugar-prompt

Statistics

Installs: 223

Dependents: 3

Suggesters: 0

Stars: 1

Open Issues: 0

v0.2.0 2026-05-07 02:01 UTC

This package is auto-updated.

Last update: 2026-05-18 22:49:23 UTC


README

sugar-prompt

SugarPrompt

CI codecov Packagist Version License PHP

demo

composer require sugarcraft/sugar-prompt

PHP port of charmbracelet/huh — interactive form library built on top of SugarCraft + SugarBits.

use SugarCraft\Prompt\Form;
use SugarCraft\Prompt\Field\{Input, Confirm, Select, Note};

$form = Form::new(
    Note::new('welcome')->title('Onboarding')->desc('A few quick questions.'),
    Input::new('name')->title('Your name?')->placeholder('Ada Lovelace'),
    Confirm::new('newsletter')->title('Subscribe to the newsletter?'),
    Select::new('lang')->title('Favorite language?')->options('PHP', 'Go', 'Rust', 'Python'),
);
// $form is a SugarCraft Model — drop it into a Program.

Every field exposes short-form aliases (title, desc, placeholder, width, height, validator, options, min, max, …). The upstream-mirroring long forms (withTitle, withDescription, …) work identically — pick whichever reads better at the call site.

Field types

Field Description Notable knobs
Input Single-line text (wraps SugarBits\TextInput) withPlaceholder, withCharLimit, withWidth, withPrompt, withValidator(\Closure), withTitleFunc / withDescriptionFunc, withPassword(bool, string $echo = '*'), withSuggestions(list<string>), withSuggestionsFunc(\Closure(string):list<string>)
Text Multi-line text editor withCharLimit, withMaxLines, withShowLineNumbers, withValidator
Confirm Yes/no boolean withAffirmative/withNegative, withValidator(\Closure(bool):?string), withTitleFunc, withDescriptionFunc
Select Single-choice list (wraps SugarBits\ItemList) withOptions(...), withTitleFunc, withDescriptionFunc
MultiSelect Multi-choice list withOptions(...), withLimit(int)
Note Read-only paragraph; skipped by tab navigation withTitle, withDescription, withHeight(int), withNext(bool), withNextLabel(string) (turns it into an interactive button page)
FilePicker Filesystem picker (wraps SugarBits\FileTree) withCwd, withAllowDirs, withAllowFiles, withShowSize, withShowHidden

All fields share a common navigation contract: Tab / advances, Shift+Tab / retreats, Enter on the last interactive field submits, Esc / Ctrl+C aborts. Skippable fields (e.g. plain Notes) are passed over silently.

Forms and groups

Form::new(...$fields) is a single-page form. For multi-page flows build with Form::groups(Group::new(...$fields), …). Each group carries its own title / description / hide-predicate / theme override:

Form::groups(
    Group::new(
        Input::new('name')->withTitle('Your name?'),
        Confirm::new('proceed')->withTitle('Continue?'),
    )->withTitle('Step 1'),
    Group::new(
        Note::new('done')->withTitle('Thanks!')->withNext()->withNextLabel('Finish'),
    )
        ->withTheme(Theme::dracula())
        ->withShowHelp(false)
        ->withHideFunc(fn (array $v) => $v['proceed'] !== true),
);

Form-level chainables

Method What it does
withTheme(Theme) Switch the colour palette.
withAccessible(bool) Render plain label: value text — for screen readers / dumb terminals.
withShowHelp(bool) Toggle the help footer.
withShowErrors(bool) Toggle the inline ! error line on validation failures.
withWidth(int), withHeight(int) Pin the rendered geometry.
withTimeout(int $ms) Auto-abort after $ms of wall clock.
keyMap(KeyMap) / withKeyMap(KeyMap) Override the bindings for Next / Prev / Submit / Quit (and per-field nav) on a single form. Mirrors upstream huh #272.

Reading values after submit

values() returns every visible field keyed by key(). For typed access call getString, getInt, getBool, getArray. For inspecting validation state during a run use errors(), hasErrors(), getFocusedField(), keyBinds(), help().

Themes & accessibility

Stock themes ship as static factories on SugarCraft\Prompt\Theme: ansi() (default), plain(), charm(), dracula(), catppuccin(), base16(). Pass one to Form::withTheme(...). The accessibility mode flips the entire form to plain-text rendering — useful when you detect NO_COLOR=1 or TERM=dumb.

Validators and dynamic labels

Every value-producing field supports withValidator(\Closure). The closure runs on every keystroke (or every value flip for Confirm) and returns null for valid or an error string. The error renders inline beneath the field and shows up in Form::errors().

Use withTitleFunc(\Closure(): string) / withDescriptionFunc(...) on any field to compute labels lazily — handy when the label depends on values from a previous group.

Validation

Field-level validation with withValidation

Input and Text fields support withValidation() for predicate-based validation — a cleaner alternative to withValidator:

$input = Input::new('email')
    ->withTitle('Email address')
    ->withPlaceholder('you@example.com')
    ->withValidation(fn($v) => filter_var($v, FILTER_VALIDATE_EMAIL), 'Must be a valid email');

$text = Text::new('bio')
    ->withTitle('Biography')
    ->withValidation(fn($v) => mb_strlen($v) >= 10, 'Must be at least 10 characters');

The predicate receives the field value and must return true for valid or false for invalid. The error message renders inline beneath the field and is collected into Form::errors().

Error summary with withErrorSummary

Enable withErrorSummary() on a Form to display all validation errors at the end when submission fails:

$form = Form::new(
    Input::new('name')
        ->withTitle('Name')
        ->withValidation(fn($v) => !empty(trim($v)), 'Name is required'),
    Input::new('email')
        ->withTitle('Email')
        ->withValidation(fn($v) => filter_var($v, FILTER_VALIDATE_EMAIL), 'Must be a valid email'),
)->withErrorSummary(true);

When enabled and the form is submitted with errors, an error summary renders above the form listing every failed field and its error message.

Test

cd sugar-prompt && composer install && vendor/bin/phpunit

Demos

Confirm

confirm

Multi-page form

form

Input

input

Multi-select

multi-select

Select

select

Text (multi-line)

text

Themes

themes