-
Notifications
You must be signed in to change notification settings - Fork 0
LayrzComboBoxInput
Autocomplete text field with searchable dropdown options.
Specification Status:
LayrzComboBoxInputhas shipped and is implemented atlib/src/inputs/src/combobox/combobox_input.dart(+combobox_surface.dart). The section below marked Shipped behavior describes the actual, current implementation.Everything below that, starting at "Metadata", is the original pre-implementation design sketch carried over from the
layrz_thememigration plan. It predates the actual implementation and does not match the shipped API in several places (for example: the shipped overlay mechanism isLayrzAnchoredPanel, notRawAutocomplete; there is noposition/LComboboxPositionparameter — desktop always covers the field viacoverAnchor: true;maxOptionsToDisplay/maxChoicesToDisplaydo not exist — see the "Dropdown height" section below). Reconciling the rest of this legacy sketch with the shipped widget is out of scope for this unit and is left for a dedicated documentation pass.
LayrzComboBoxInput is rebuilt on LayrzAnchoredPanel (desktop, >= 960px) and covers its
anchor exactly the way LayrzSelectInput does (coverAnchor: true, widthPolicy: matchAnchor) —
the "elevated field" illusion (DESIGN-145): the opened panel appears exactly on top of the closed
field, same position and width, rather than beside it. Below 960px it opens a bottom sheet
instead. The former hand-rolled RawMenuAnchor overlay, its own layout delegate, and its own
background/shadow painting are gone.
The panel's first row IS the live text input — not a second search field. The closed field and
the panel's first row share the exact same TextEditingController and FocusNode instances; when
the panel opens, that one live LayrzEditableField element is reparented from the closed anchor's
slot into the panel's first row within the same frame, rather than being unmounted and rebuilt.
Typing continues into the panel with no character loss and no caret jump. The panel row draws no
border of its own — the border that reads as the field's own comes from LayrzAnchoredPanel's
border parameter (LayrzAnchoredPanelBorder), painted around the panel's outer decorated box,
colored primary (or danger when errors is non-empty), matching LayrzSelectInput.
There is no "Use "…"" confirmation row (BREAKING). An earlier version of the open panel
rendered a confirmation row above the suggestions that had to be tapped (or reached via
arrow-down) to commit typed text as the value. It is gone: the typed text is already the value,
reported live through onChanged as the user types, and the options below are suggestions, not a
required choice. allowFreeForm keeps its existing meaning otherwise. Keyboard navigation shifted
accordingly — arrow-down now lands on the first suggestion instead of on the removed confirmation
row.
Label and error text render outside the anchor. Both used to render inside the widget handed to
LayrzAnchoredPanel as its anchor, which grew the anchor's rect upward by the label's own height —
so the panel opened covering the label instead of the field beneath it. They now compose around the
anchor instead (mirroring LayrzSelectInput._appendExtras), so the panel aligns to the field. The
error footer is not gated on labelText being non-null: a field with errors and no label still
renders its error text.
| Property | Value |
|---|---|
| Mirror |
ThemedTextInput with enableCombobox: true
|
| Phase | M3 Inputs |
| Domain | Inputs |
| SDK Primitive |
RawAutocomplete (SDK: autocomplete.dart) + EditableText with custom TextSelectionControls
|
Architectural question: LayrzComboBoxInput differs fundamentally from picker-style inputs and even from plain LayrzTextInput. It composes an EDITABLE text field (not read-only like pickers) combined with a dropdown overlay showing filtered options. This is distinct from:
- LayrzTextInput: Static text editing, no autocomplete
- LayrzSelectInput: Read-only field opening a modal with single selection
- LayrzTextAreaInput: Multiline editable text, no autocomplete
Should LayrzComboBoxInput:
- Inherit the Input Contract's standard API (label, prefix, suffix, help, etc.)?
- Or is it a separate implementation pattern?
-
Value type:
String— the user-entered or selected text - Interaction pattern: An editable text field with a dropdown overlay showing options that match the user's input
- Selection options: Options from a provided list, filtered by the user's typed text
-
Confirmation: User can either:
- Click an option to select it (auto-fills the field)
- Continue typing and press Enter to submit the manual entry
- Blur the field to finalize (behavior configurable)
// Design sketch — illustrative only
class LayrzComboBoxInput extends StatefulWidget {
/// The current text content (user-typed or selected).
final String? value;
/// Callback invoked when the field's text actually changes — typing, selecting
/// an option that differs from the current text, or an external value update.
/// Does NOT re-fire when a commit does not change the text (e.g. re-selecting
/// the option already shown). See "Selection callbacks" below.
final void Function(String)? onChanged;
/// Callback invoked on every commit — selecting an option or pressing Enter —
/// unconditionally, including a same-value re-selection. See "Selection
/// callbacks" below.
final void Function(String)? onSubmit;
/// List of autocomplete options.
/// These strings are matched against the user's input for filtering.
final List<String> options;
/// Whether to enable autocomplete filtering.
/// If false, all options are always displayed.
final bool enableAutocomplete;
/// Text displayed when the filtered options list is empty.
final String emptyOptionsText;
/// Position of the dropdown: above or below the field.
final LComboboxPosition position;
/// Placeholder text shown when the field is empty.
final String? placeholder;
/// Keyboard type (text, URL, email, etc.).
final TextInputType keyboardType;
/// Text input action (e.g., TextInputAction.done, .next).
final TextInputAction textInputAction;
const LayrzComboBoxInput({
required this.options,
this.value,
this.onChanged,
this.onSubmit,
this.enableAutocomplete = true,
this.emptyOptionsText = 'No options',
this.position = .below,
this.placeholder,
this.keyboardType = .text,
this.textInputAction = .done,
// ... shared contract parameters (label, prefix, suffix, help, etc.)
});
}
enum LComboboxPosition {
/// Dropdown appears below the text field.
below,
/// Dropdown appears above the text field.
above,
}There is no maxOptionsToDisplay (or any other height-related) parameter. The
desktop overlay's option list is capped at a fixed 300 logical pixels and scrolls
past that — the same rule applied to LayrzSelectInput's and LayrzDurationInput's
overlays. A caller that needs the panel taller or shorter than 300px cannot ask for
it; this is a deliberate capability trade against a parameter that previously
produced an incorrect panel height for any value other than its own default.
onChanged and onSubmit answer two different questions and can disagree on a
single selection:
-
onChanged— "did the field's text change?" Fires once when it does (typing, selecting a different option, an external value push). Stays silent when a commit does not change the text, including re-selecting the option already shown. -
onSubmit— "did the user just commit a value?" Fires on every commit, unconditionally, same value or not.
A caller that needs to react to "the user made a selection" — even a repeated one
— should use onSubmit, not onChanged.
Inherits some Input Contract chrome, but with differences:
// Design sketch — shared with LayrzTextInput (editable variant):
class LayrzComboBoxInput {
/// Label displayed above the field.
final String? labelText;
/// Custom label widget (mutually exclusive with labelText).
final Widget? label;
/// Placeholder/hint text (shown when empty).
final String? placeholder;
/// Icon in the prefix slot.
final IconData? prefixIcon;
/// Custom widget in the prefix slot (mutually exclusive with prefixIcon).
final Widget? prefixWidget;
/// Callback when prefix is tapped.
final VoidCallback? onPrefixTap;
/// Icon in the suffix slot (often a dropdown arrow).
final IconData? suffixIcon;
/// Custom widget in the suffix slot (mutually exclusive with suffixIcon).
final Widget? suffixWidget;
/// Callback when suffix is tapped.
final VoidCallback? onSuffixTap;
/// Help title for tooltip.
final String? helpTitleText;
/// Help content for tooltip.
final String? helpContentText;
/// Selects the padding ramp (see [Input-Contract](Input-Contract)).
///
/// Default: `pd2`/10px. `dense: true`: `pd1`/6px. Identical on every
/// viewport. Both the closed field and the open panel row read the same
/// chrome-owned inset. No public `padding` override (D66).
final bool dense;
/// Error messages to display below the field.
final List<String> errors;
/// Whether to display errors and help text.
final bool hideDetails;
/// Focus node for managing focus.
final FocusNode? focusNode;
/// Text controller for external value management.
final TextEditingController? controller;
/// Whether the field is disabled.
final bool disabled;
/// Character input formatters.
final List<TextInputFormatter> inputFormatters;
}ThemedTextInput with enableCombobox: true (source: lib/src/inputs/src/general/text_input.dart):
| Parameter | Type | Notes |
|---|---|---|
value |
String? |
Current text content |
onChanged |
void Function(String)? |
Text change callback |
onSubmitted |
VoidCallback? |
Submit callback |
controller |
TextEditingController? |
Text controller |
choices |
List<String> |
Autocomplete option list |
enableCombobox |
bool |
Enable combobox mode (default: false) |
maxChoicesToDisplay |
int |
Max options shown (default: 5) |
emptyChoicesText |
String |
Empty list message (default: "No choices") |
position |
ThemedComboboxPosition |
Dropdown position: above or below (default: below) |
labelText |
String? |
Label text |
label |
Widget? |
Label widget |
placeholder |
String? |
Placeholder text |
prefixIcon |
IconData? |
Icon in prefix slot |
prefixWidget |
Widget? |
Custom prefix widget |
onPrefixTap |
VoidCallback? |
Prefix tap handler |
suffixIcon |
IconData? |
Icon in suffix slot |
suffixWidget |
Widget? |
Custom suffix widget |
onSuffixTap |
VoidCallback? |
Suffix tap handler |
errors |
List<String> |
Error messages |
hideDetails |
bool |
Hide errors/help text |
padding |
EdgeInsets? |
Field padding |
disabled |
bool |
Disable the field |
focusNode |
FocusNode? |
Focus node |
keyboardType |
TextInputType |
Keyboard type (default: text) |
textInputAction |
TextInputAction? |
Enter key behavior |
inputFormatters |
List<TextInputFormatter> |
Input formatters |
autofillHints |
List<String> |
Autofill hints |
autocorrect |
bool |
Spell check (default: true) |
enableSuggestions |
bool |
Suggestions (default: true) |
textStyle |
TextStyle? |
Custom text style |
ThemedComboboxPosition enum:
-
below: Dropdown below the field -
above: Dropdown above the field
- RawAutocomplete (SDK) — the core autocomplete overlay mechanism
-
EditableText + custom TextSelectionControls — CRITICAL BLOCKER. Same as LayrzTextInput and LayrzTextAreaInput. Requires:
- Text selection (tap and drag)
- Copy/paste (long-press or keyboard)
- Cut operations
- Desktop selection toolbar
- Mobile selection handles with magnifier
-
Overlay focus behavior: The
RawAutocompleteoverlay is excluded from ambient focus traversal, so Tab navigation skips it. - Option filtering: Options are filtered case-insensitively by matching the start of each option against the user's typed text.
-
Dropdown positioning: The dropdown is smart-positioned based on available screen space. If
positionisbelowbut there's no room, it movesabove. - Option selection: Clicking an option fills the field with the option text.
- Manual entry: User can type any text, not just options. Pressing Enter or Tab submits the manual entry.
- Blur behavior: When the field loses focus, the dropdown closes (behavior may be configurable).
-
Separate component or factory?: Should LayrzComboBoxInput be:
- A distinct widget class (recommended for clarity)?
- A factory constructor of LayrzTextInput (e.g.,
LayrzTextInput.combobox())? - Or a parameter-driven variant (e.g.,
LayrzTextInput(enableCombobox: true))?
-
Option matching and filtering: Does option matching:
- Match from the start of each option?
- Match anywhere in the option?
- Use a custom filter function?
- Case-sensitive or case-insensitive?
-
Option rendering: Are options:
- Plain text strings?
- Can they include icons, colors, or custom widgets?
- Is there a callback to format option display?
-
Value type flexibility: Currently, options and value are both
String. Should there be a genericLayrzComboBoxInput<T>variant where:- Options are of type
T - A
toString()or custom formatter convertsTto display string?
- Options are of type
-
Free-form entry: Should the field allow any text the user types, or only text from the options list?
-
Dropdown open/close control: Can the caller programmatically open/close the dropdown, or is it automatic?
-
Keyboard navigation in dropdown: Are arrow keys, Enter, and Escape supported to navigate and select options?
-
Accessibility: Should the dropdown be labeled for screen readers? Should the combobox role be applied?
-
Material-free TextSelectionControls: Same CRITICAL BLOCKER as for LayrzTextInput. What is the implementation plan?
-
Distinguishing from LayrzSelectInput: When should a user choose LayrzComboBoxInput (editable, free-form) vs. LayrzSelectInput (read-only, restricted to options)? Should documentation clarify the use cases?
Last updated: 2026-08-26 (v0.0.13 — documented the shipped implementation: LayrzAnchoredPanel
with coverAnchor: true, the panel's first row as the same live field reparented from the closed
anchor, removal of the Use "…" confirmation row (BREAKING), and label/error text hoisted outside
the anchor; ff72bec on 2026-08-25 previously documented the removal of maxOptionsToDisplay and
the fixed 300px overlay height, which stands unchanged; the remaining pre-implementation sections
are unchanged and still need a full documentation pass)
Related documents: Input Contract, Flutter 347 Audit, Component Catalog, Design Tokens
Made with ❤️ by Golden M, Inc.
- LayrzAnchoredPanel
- LayrzBottomSheet
- LayrzDialog
- LayrzDropdownMenu
- LayrzResponsiveModal
- LayrzPageTransition
- LayrzTextInput
- LayrzSelectableAction
- LayrzSelectionToolbar
- LayrzTextSelectionControls
- LayrzSelectionMagnifier
- LayrzSelectionHandlePainter
- LayrzTextAreaInput
- LayrzComboBoxInput
- LayrzNumberInput
- LayrzPasswordInput
- LayrzCheckboxInput
- LayrzRadioInput
- LayrzSelectInput
- LayrzMultiSelectInput
- LayrzSearchInput
- LayrzDualListInput
- LayrzDurationInput
- LayrzSlider
- LayrzStepper