-
Notifications
You must be signed in to change notification settings - Fork 0
LayrzTextInput
A Material-free, single-line text input field with optional label and hint text, prefix/suffix slots, error display, help affordance, and keyboard shortcut badging.
Metadata
Mirrors: ThemedTextInput (layrz_theme)
Phase: M2 (Core primitives)
Domain: Inputs
Primitive: EditableText
Status: Shipped in 0.0.9.
LayrzTextInput is the foundational text input widget and the base of the entire M3+ input family. Every other Layrz*Input (e.g., LayrzNumberInput, LayrzSelectInput) composes LayrzTextInput internally rather than reimplementing the field chrome. Picker-style inputs (date, time, color, emoji, etc.) render as a read-only LayrzTextInput that opens their selection surface on tap.
- Filled visual style only — single-line, consistent with Material 3 conventions, with a filled background and border
-
Label or hint is mandatory — at least one of
labelTextorhintTextmust be non-null; both together are valid; neither alone is valid (debug assertion enforced) - Label is static — displayed above the field, never animated, never repositioned
- Hint text is display-only — shown inside the empty field when there is no label, disappears on focus or input
-
Optional label icon — an icon rendered beside the label text via
RichText, inheriting label styling -
Prefix and suffix are mutually exclusive per slot — at most one of
prefixIcon,prefix, orprefixTextper slot; the same rule applies to the suffix trio -
Errors are caller-owned — no built-in
validatorcallback; callers provide aList<String>of error messages joined with", "into a single line -
Disabled and read-only are distinct — disabled blocks all taps and shows muted text; read-only fires
onTap(used by pickers) and displays a lock icon in the suffix - Selection handles and toolbar are deferred — text selection and keyboard selection work, but touch drag handles, selection toolbar, and magnifier are DESIGN-74 scope
The six interaction states of the field, their visual treatment, and border rendering:
| State | Fill | Border (always 1.5px) | Text | Dashed |
|---|---|---|---|---|
| Rest | surface2 |
transparent |
fg1, hint fg3
|
false |
| Hover | surface3 |
transparent | fg1 |
false |
| Focus | surface2 |
colors.primary |
fg1 |
false |
| Error | colors.danger.shade50 |
colors.danger |
fg1 |
false |
| Disabled | surface2 |
divider |
fg4 |
true |
| Read-only | surface2 |
divider |
fg1 + lock icon |
false |
State precedence: disabled > read-only > error > pressed > hover/focused > default.
Key differences:
- Focus preserves the rest fill (surface2) and only changes the border to primary
- Read-only is the rest state plus a lock icon in the suffix (solid border, full-contrast text)
- Disabled darkens text to fg4 and uses a dashed border to signal modal state
- Dashed border applies to disabled only, not read-only
- Error state uses danger-coloured fill and border
The constructor requires at least one of labelText or hintText:
class LayrzTextInput extends StatefulWidget {
/// The label text displayed above the input field (optional if hintText is provided).
///
/// When provided, rendered in `label` typography, `fg2` colour, static above the field.
/// At least one of [labelText] or [hintText] must be non-null.
final String? labelText;
/// Optional icon rendered beside the label text via RichText.
///
/// Inherits the label's colour (fg2) and size. Sits to the left of the label text.
final IconData? labelIcon;
/// Hint text displayed inside the empty field when there is no label (optional if labelText is provided).
///
/// Rendered in the field's text style but with `fg3` (muted) colour.
/// Disappears when the field receives focus or the user begins typing.
/// At least one of [labelText] or [hintText] must be non-null.
final String? hintText;
// ... rest of parameters
}Assertion: labelText != null || hintText != null. Both can be non-null; neither alone is an error.
-
labelText (String?, optional) — label displayed above the field. At least one of this or
hintTextmust be non-null. - labelIcon (IconData?, optional) — icon rendered beside label text
-
hintText (String?, optional) — hint inside the empty field. At least one of this or
labelTextmust be non-null. -
isRequired (bool, default false) — when true, a red
*appears next to the label text - prefixIcon, prefix, prefixText (mutually exclusive) — prefix slot content
- suffixIcon, suffix, suffixText (mutually exclusive) — suffix slot content
-
errors (List, default []) — error messages, joined with
", "into a single line below the field - hideDetails (bool, default false) — when true, hides error text (field error state remains)
-
disabled (bool, default false) — blocks all input and taps, darkens text to
fg4, uses dashed border -
readOnly (bool, default false) — blocks editing but fires
onTap; displays lock icon in suffix - onChanged, onSubmit, onFocusChanged, onTap — interaction callbacks
- controller, focusNode — text and focus management (caller-owned if supplied)
- keyboardType, textInputAction, inputFormatters, maxLength, autofocus, textCapitalization, autocorrect, enableSuggestions — standard EditableText passthroughs
- shortcut (Set?, optional) — keyboard shortcut badge (display-only, hidden on mobile)
- padding (EdgeInsets?, optional) — custom padding; defaults to spacing tokens
- helpTitleText, helpContentText (optional) — help affordance tooltip
-
Label or hint is mandatory — at least one of
labelTextorhintTextmust be non-null (debug assertion) - Slot exclusivity — at most one of three forms per slot; assertion enforced in debug mode
-
Read-only lock icon — always appears in the suffix when
readOnly: true, coexisting with caller-supplied suffixes -
Error text is single-line — multiple errors are joined with
", "(comma-space), not rendered as a bulleted list -
Error icon coexists — the error
!icon is independent and always appears iferrorsis non-empty - Shortcut is display-only — keyboard shortcuts badge is rendered but does not bind key events
-
Single-line only —
maxLinesis always 1; multiline textarea is separate component (LayrzTextAreaInput) - Geometry is byte-identical — border width, padding, height, radius are identical in all six states (D15)
- Selection overlay is invisible — caret, selection via keyboard, and Ctrl+A/Ctrl+C work; touch handles and toolbar are DESIGN-74 scope
-
M1 Theme System (
LayrzTheme,LayrzThemeData,LayrzTokens) -
M2 Tooltips (
LayrzTooltip) — for help affordances - layrz_icons (^1.1.1) — for error and lock icons
-
Flutter 3.47+ —
EditableTextand widget state API
LayrzTextInput(
labelText: 'Email',
hintText: 'user@example.com',
keyboardType: TextInputType.emailAddress,
onChanged: (value) => setState(() => email = value),
)LayrzTextInput(
hintText: 'Search...',
prefixIcon: LayrzIcons.solarOutlineSearch,
onChanged: (value) => setState(() => query = value),
)LayrzTextInput(
labelText: 'Username',
labelIcon: LayrzIcons.solarOutlinePerson,
isRequired: true,
errors: username.isEmpty ? ['Username is required'] : [],
onChanged: (value) => setState(() => username = value),
)LayrzTextInput(
labelText: 'Date',
readOnly: true,
controller: TextEditingController(text: selectedDate?.toString() ?? ''),
onTap: () => _showDatePicker(), // Opens date picker
)LayrzTextInput(
labelText: 'Locked Field',
disabled: true,
controller: TextEditingController(text: 'This field is disabled'),
)LayrzTextInput(
labelText: 'Password',
isRequired: true,
obscureText: true,
errors: _validatePassword(password), // Returns ['too short', 'no capitals'] → joins as 'too short, no capitals'
onChanged: (value) => setState(() => password = value),
)LayrzTextInput is the foundational input component. All concrete input types compose it, ensuring visual and behavioral consistency across the entire input family.
Last updated: 2026-08-18
Related documents: Input Contract, Milestone 2, Design Tokens, Decisions (D32, D33, D34), Architecture
Made with ❤️ by Golden M, Inc.
- LayrzTextInput
- LayrzSelectableAction
- LayrzSelectionToolbar
- LayrzTextSelectionControls
- LayrzSelectionMagnifier
- LayrzSelectionHandlePainter
- LayrzTextAreaInput
- LayrzComboBoxInput
- LayrzNumberInput
- LayrzPasswordInput
- LayrzCheckboxInput
- LayrzRadioInput
- LayrzSelectInput
- LayrzMultiSelectInput
- LayrzSearchInput
- LayrzDualListInput
- LayrzDurationInput
- LayrzSlider