Interactive forms — Note, Input, Confirm, Select, MultiSelect, Text, FilePicker
Form library built on SugarCraft + SugarBits. Multi-page Group wizards, six themes, per-field validators, conditional skip predicates. The form is itself a Model — drop it into a Program.
composer require sugarcraft/sugar-prompt
use SugarCraft\Prompt\Form;
use SugarCraft\Prompt\Field\{Input, Confirm, Select, Note};
use SugarCraft\Prompt\Validator\{Required, Email, MinLength};
use SugarCraft\Prompt\Fuzzy\FuzzyMatcher;
$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),
);
// FuzzyMatcher API:
$matcher = new FuzzyMatcher();
$score = $matcher->score('js', 'JavaScript'); // 9
$matches = $matcher->match('py', ['Python', 'PHP', 'Ruby']); // [['Python', 8], ...]
->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.->withFuzzySuggestions([...]) on Input and Select — Smith-Waterman scoring ranks candidates by match quality. Use FuzzyMatcher directly for programmatic scoring: $matcher->score($query, $candidate).->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.j / k navigate, Space toggles selection — mirrors vim-style keybindings.Select::withEnum(\BackedEnum::class) coerces the selected value to the given enum type automatically.Group::new() stacks Forms into a wizard. Conditional ->withSkipFn().Theme::$errorSummary.$form->getValue('name') after the Program ends — typed value retrieval.Form::keyMap($keymap) rebinds Next / Prev / Submit / Quit per form (mirrors huh #272).| Class | Method | Description |
|---|---|---|
| Form | new(...fields) | Create a new form |
| Form | getValue(name) | Get field value after submit |
| Form | validateAll() | Run all field validators, return [fieldKey => errorMessage] |
| Form | withErrorSummary(bool) | Show all errors at once on submit failure |
| Input | new(name) | Create input field |
| Input | withFuzzySuggestions(list) | Filter suggestions by fuzzy match score |
| Input | withAsyncSuggestions(callable, int = 150) | Async suggestions with debounce |
| Confirm | new(name) | Create confirm field |
| Select | new(name) | Create select field |
| Select | withFuzzySuggestions(list) | Filter options by fuzzy match score |
| Select | withAsyncSuggestions(callable, int = 150) | Async suggestions with debounce |
| Select | withEnum(BackedEnum::class) | Coerce selected value to enum type |
| MultiSelect | new(name) | Create multi-select field (j/k nav, Space toggle) |
| Note | new(content) | Create note field |
| Group | new(...forms) | Create wizard group |
| FuzzyMatcher | score(query, candidate) | Smith-Waterman alignment score (int) |
| FuzzyMatcher | match(query, candidates) | Ranked matches list<[string,int]> |
| Theme | errorSummary | Renderable slot for custom error summary display |
VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.