Skip to content

LayrzTextInput

Kenny Mochizuki Escalona edited this page Aug 19, 2026 · 12 revisions

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.


Overview

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.

Design Principles

  • 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 labelText or hintText must 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 when the field has text
  • 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, or prefixText per slot; the same rule applies to the suffix trio
  • Errors are caller-owned — no built-in validator callback; callers provide a List<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

Interaction States

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

API Structure

Core Constructor (Simplified)

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 has text.
  /// 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.

Key Parameters

  • labelText (String?, optional) — label displayed above the field. At least one of this or hintText must 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 labelText must 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

Key Invariants

  • Label or hint is mandatory — at least one of labelText or hintText must 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 if errors is non-empty
  • Shortcut is display-only — keyboard shortcuts badge is rendered but does not bind key events
  • Single-line onlymaxLines is 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

Dependencies

  • 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+EditableText and widget state API

Usage Examples

Basic with Label

LayrzTextInput(
  labelText: 'Email',
  hintText: 'user@example.com',
  keyboardType: TextInputType.emailAddress,
  onChanged: (value) => setState(() => email = value),
)

Search with Hint Only (No Label)

LayrzTextInput(
  hintText: 'Search...',
  prefixIcon: LayrzIcons.solarOutlineSearch,
  onChanged: (value) => setState(() => query = value),
)

With Label Icon and Required Flag

LayrzTextInput(
  labelText: 'Username',
  labelIcon: LayrzIcons.solarOutlinePerson,
  isRequired: true,
  errors: username.isEmpty ? ['Username is required'] : [],
  onChanged: (value) => setState(() => username = value),
)

Read-Only (Picker Style)

LayrzTextInput(
  labelText: 'Date',
  readOnly: true,
  controller: TextEditingController(text: selectedDate?.toString() ?? ''),
  onTap: () => _showDatePicker(),  // Opens date picker
)

Disabled Field

LayrzTextInput(
  labelText: 'Locked Field',
  disabled: true,
  controller: TextEditingController(text: 'This field is disabled'),
)

With Error Messages

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),
)

Architectural Role

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

Clone this wiki locally