← All 40 libraries

SugarForms

🍬 SugarForms

Interactive forms + foundation form primitives

inspired by charmbracelet/huh foundation forms inputs wizards validation

SugarForms code coverage

Form engine built on SugarCraft — multi-page Group wizards, six themes, per-field validators, conditional skip predicates. The form is itself a Model — drop it into a Program. Also the foundation for the raw form / input primitives that sugar-widgets re-exports, folded together with the interactive form library (port of charmbracelet/huh) into a single standalone dependency.

Install

composer require sugarcraft/sugar-forms

Quickstart

use SugarCraft\Forms\Form;
use SugarCraft\Forms\Field\{Input, Confirm, Select, Note};
use SugarCraft\Forms\Validator\{Required, Email, MinLength};
use SugarCraft\Fuzzy\Matcher\SmithWatermanMatcher;

$form = Form::new(
    Note::new('welcome')
        ->withTitle('Onboarding')
        ->withDescription('A few quick questions.'),
    Input::new('name')
        ->withTitle('Your name?')
        ->withPlaceholder('Ada Lovelace')
        ->withValidator(new Required())
        ->withValidator(new MinLength(2)),
    Input::new('email')
        ->withTitle('Email address')
        ->withPlaceholder('you@example.com')
        ->withValidator(new Required())
        ->withValidator(new Email()),
    Select::new('lang')
        ->withTitle('Favorite language?')
        ->withOptions('PHP', 'Go', 'Rust', 'Python')
        ->withFuzzySuggestions(['PHP', 'Go', 'Rust', 'Python', 'JavaScript', 'TypeScript']),
);
// $form is a SugarCraft Model — drop it into a Program.

// Async suggestions:
$form = Form::new(
    Input::new('repo')
        ->withTitle('Repository')
        ->withAsyncSuggestions(fn($q) => fetchRepos($q), 150),
);

// Fuzzy scoring — sugar-fuzzy, the engine behind withFuzzySuggestions():
$matcher = SmithWatermanMatcher::new();
$score = $matcher->score('js', 'JavaScript');                   // 3
$best  = $matcher->matchAll('py', ['Python', 'PHP', 'Ruby'])[0]; // MatchResult: Python, score 11

What's in the box

Input primitivesTextInput, TextArea, ItemList, Viewport, FilePicker, Cursor, Scrollbar, Spinner.
Form engineField interface + Field{Input, Text, Confirm, Select, MultiSelect, Note, FilePicker, Date, Slider, Color}, Form, Group, KeyMap, Theme.
Hidden fields & groupsForm honours Field::isHidden() in values, errors, validation, navigation, submit and view; focus never rests on a hidden field and fully hidden groups are skipped. A Group::withHideFunc() predicate sees the same view values() returns — the visible fields of every visible earlier group.
Bounded editingTextInput / TextArea clip a paste or insert to the remaining charLimit budget (newlines count), and TextArea pans to keep the cursor row visible.
Validation->withValidate(fn($v) => …) — return null to pass, error string to block submit. Five built-in validators: Required, Email, MinLength, MaxLength, Pattern. Chain multiple with ->withValidator(new Validator()). Cross-field validation via Form::validateAll(): array<string,string> after submit. Enable error summary with Form::withErrorSummary(true) to show all errors at once.
Fuzzy suggestions->withFuzzySuggestions([...]) on Input and Select — Smith-Waterman scoring ranks candidates by match quality (extracted to sugar-fuzzy). Use sugar-fuzzy’s SmithWatermanMatcher directly for programmatic scoring: $matcher->score($query, $candidate).
Async suggestions->withAsyncSuggestions(callable $fetcher, int $debounceMs = 150) on Input and Select — fetches suggestions asynchronously with a configurable debounce delay. The SuggestionsReadyMsg message is dispatched via the event loop when results are available.
MultiSelect vim keysj / k navigate, Space toggles selection — mirrors vim-style keybindings.
Select enum modeSelect::withEnum(\BackedEnum::class) coerces the selected value to the given enum type automatically.
Multi-page GroupsGroup::new() stacks Forms into a wizard. Conditional ->withSkipFn().
Six themesDefault, charm, dracula, base16, catppuccin, pink. Custom error summary slot via Theme::$errorSummary.
Tab navTab / Shift+Tab between fields, Enter on the last submits, Esc / Ctrl+C aborts.
Submit hook$form->getValue('name') after the Program ends — typed value retrieval.
Form-level KeyMapForm::keyMap($keymap) rebinds Next / Prev / Submit / Quit per form.
ActionSpinnerFork-isolated spinner action (ActionSpinner) with a bounded runtime ceiling — the long-running spin primitive folded in from the retired prompt façade.

Use it for

Source & demos

Try the quickstart →

API

ClassMethodDescription
Formnew(Field ...$fields)Create a single-group form with fields
Formgroups(Group ...$groups)Create a multi-page form
Formvalues() / getString() / getInt() / getBool() / getArray()Submitted values of visible fields; hidden fields and hidden groups are excluded
FormgetValue(name)Get field value after submit
FormvalidateAll()Run all field validators, return [fieldKey => errorMessage]
FormwithErrorSummary(bool)Show all errors at once on submit failure
FormkeyMap($keymap)Rebind Next / Prev / Submit / Quit per form
Groupnew(Field ...$fields) / withHideFunc(?\Closure(array): bool)A page of fields; the predicate receives the visible values collected before this group
Field\Inputnew(string)Create a text input field
Field\InputwithFuzzySuggestions(list)Filter suggestions by fuzzy match score
Field\InputwithAsyncSuggestions(callable, int = 150)Async suggestions with debounce
Field\Confirmnew(string)Create a confirmation field
Field\Selectnew(string)->withOptions(string ...)Create a select field
Field\SelectwithFuzzySuggestions(list)Filter options by fuzzy match score
Field\SelectwithAsyncSuggestions(callable, int = 150)Async suggestions with debounce
Field\SelectwithEnum(BackedEnum::class)Coerce selected value to enum type
Field\MultiSelectnew(name)Create multi-select field (j/k nav, Space toggle)
Field\Notenew(content)Create note field
Validator\Requiredvalidate(mixed)Check value is not empty
Validator\Emailvalidate(mixed)Check valid email format
Spinner\Spinnernew(?Style)Create a spinner with style
ActionSpinnernew() / run()Fork-isolated spinner action with a bounded runtime ceiling
Fuzzy\FuzzyMatcher (deprecated shim)score(query, candidate)Smith-Waterman alignment score (int) — delegates to sugar-fuzzy
Fuzzy\FuzzyMatcher (deprecated shim)match(query, candidates): list<[string, int]>Legacy ranking shape — for ?MatchResult / matchAll() use sugar-fuzzy's SmithWatermanMatcher directly
ThemeerrorSummaryRenderable slot for custom error summary display

Demos.

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

Multi-page form

Multi-page form

Wizard across multiple Groups.
Input

Input

Single-line text with validation.
Confirm

Confirm

Y/N boolean.
Select

Select

Single-choice list.
MultiSelect

MultiSelect

Toggle multiple options.
Text

Text

Multi-line text input.
Themes

Themes

All six form themes side-by-side.