Option groups, separators and disabled options
Every field that draws a list - select, search, suggest, toggle and reorder, and the ->multiple() variants of the first two - accepts more than a flat one. Alongside the ->options(['value' => 'Label']) map shorthand, you can declare options one at a time, mark them disabled, and add non-selectable structure. Declaring any of them on a kind that shows no list throws where it is written.
$p->select('item', 'Item')
->heading('Fruit') // A non-selectable group heading.
->option('apple', 'Apple') // value => label
->option('banana', 'Banana')
->separator() // A non-selectable divider row.
->heading('Vegetable')
->option('carrot', 'Carrot')
->option('cherry', 'Cherry', disabled: TRUE, disabled_reason: 'out of season');
Runnable scripts: playground/02-fields-select-groups.php and select-multiple-groups.php.
Builder methods
| Name | Description |
|---|---|
option($value, $label = '', ...) | Add one selectable row. The label defaults to the value; re-declaring a value replaces it in place. Pass disabled: TRUE (with an optional disabled_reason) to show it but block selection, or description: for a contextual line shown when it is highlighted. |
options([$value => $label]) | Add many selectable rows from a map - shorthand for repeated option(). |
heading($label) | Add a non-selectable group-heading row. |
separator() | Add a non-selectable divider row. |
Behavior
On a select and a search, headings, separators and disabled options are visual only: navigation skips them, so the cursor lands only on selectable options, and they can never be highlighted or selected. A disabled option shows its reason beside the label, dimmed.
The other three kinds draw no such structure, each for its own reason. A suggest matches typed text against its selectable values rather than drawing a list to walk, so a heading or a separator is accepted where it is written and simply never reaches the field. A toggle is exactly two values and a reorder is a ranking of plain ones, so either refuses a structural row when the form is built, naming the field. Every kind is theme-driven: a heading and a disabled label are drawn by fieldEntry(), a divider by fieldEntrySeparator() and a disabled reason by fieldEntryNote(), so overriding one of those elements restyles it.
Non-selectable rows never leak into the answer: a disabled value is dropped from a multiple-choice default, absent from the collected value, and excluded from the JSON schema (Tui::schema() lists selectable options only). Supplying a disabled - or otherwise unknown - option value headlessly (via --prompts JSON or an environment override) fails collection with a clear error naming the value.
Examples
A single-choice select with a group heading, a separator and a disabled option (its reason shown beside the dimmed label):
| ANSI | No ANSI | |
| Unicode | ||
| ASCII |
A multiple select where the cursor and Space skip the separator and the disabled option, which can never be checked:
| ANSI | No ANSI | |
| Unicode | ||
| ASCII |