Skip to content

Input Contract

Kenny Mochizuki Escalona edited this page Aug 13, 2026 · 13 revisions

Input Contract

This document defines the shared contract that every Layrz*Input component in layrz_ui conforms to. Rather than specifying this per-component, the contract is recorded here as the single source of truth for input field behavior, styling, and API, so the architectural decisions need to be made only once.


Central Architectural Fact: Composition Over Reimplementation

LayrzTextInput is the base of the entire input family. Every other Layrz*Input composes LayrzTextInput internally rather than reimplementing the field chrome. This is the same architectural pattern used in layrz_theme.

The picker-style inputs (date, time, color, emoji, etc.) render as a read-only LayrzTextInput that opens their selection surface on tap.

Consequences

  • Shipping order: LayrzTextInput must ship first in M3. Every other input depends on it.
  • Visual decisions are global: The chrome of LayrzTextInput — label, prefix/suffix, help affordance, padding, error display, focus state, border decoration — is the chrome of every input in the system. Design decisions made for LayrzTextInput propagate to all inputs.
  • No separate input-container: LayrzTextInput itself plays the role of the input container. There is no separate wrapper component; all input chrome is part of LayrzTextInput.

Naming Convention: *Input Replaces *Picker

Every form field in layrz_ui is named Layrz*Input, regardless of how it collects its value. The *Picker suffix from layrz_theme is retired entirely.

Renamed Components

layrz_theme Name layrz_ui Name Collection Method
ThemedDatePicker LayrzDateInput Calendar dialog
ThemedTimePicker LayrzTimeInput Time picker dialog
ThemedDateTimePicker LayrzDateTimeInput Calendar + time in tabbed dialog
ThemedMonthPicker LayrzMonthInput Month + year grid
ThemedEmojiPicker LayrzEmojiInput Emoji selection dialog
ThemedColorPicker LayrzColorInput Color wheel/palette dialog
ThemedFilePicker LayrzFileInput System file browser
ThemedIconPicker LayrzIconInput Icon selection dialog
ThemedAvatarPicker LayrzAvatarInput System image picker

Rationale

The *Picker suffix suggests a distinct interaction pattern from form fields. In layrz_ui, all of these components are inputs that participate in forms like any text field or select field. They are not separate pickers; they are inputs with selection surfaces. The unified naming clarifies this conceptual unity.


The Shared Contract

Every Layrz*Input exposes the following API, inherited from LayrzTextInput.

Field Identification and Labels

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// The human-readable label displayed above the input field.
  /// 
  /// This is the only label representation; a `label` Widget parameter is not supported.
  final String labelText;

  /// Contextual information shown inside the empty field as placeholder text.
  /// 
  /// Disappears when the field receives focus or the user begins typing.
  final String? placeholder;

  // ...
}
  • labelText (String) only. A label Widget parameter is NOT supported.
  • placeholder — shown inside the empty field, disappears on focus or input.

Prefix and Suffix

Each input supports both a leading prefix and a trailing suffix. Each can contain either an icon or an arbitrary widget, but not both simultaneously.

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// Icon displayed in the prefix slot.
  /// 
  /// Mutually exclusive with [prefixWidget]. If both are supplied,
  /// a debug assertion will fail.
  final IconData? prefixIcon;

  /// Custom widget displayed in the prefix slot.
  /// 
  /// Mutually exclusive with [prefixIcon].
  final Widget? prefixWidget;

  /// Callback invoked when the user taps the prefix.
  /// 
  /// Called for both [prefixIcon] and [prefixWidget]; only relevant if one is supplied.
  final VoidCallback? onPrefixTap;

  /// Icon displayed in the suffix slot.
  /// 
  /// Mutually exclusive with [suffixWidget]. If both are supplied,
  /// a debug assertion will fail.
  final IconData? suffixIcon;

  /// Custom widget displayed in the suffix slot.
  /// 
  /// Mutually exclusive with [suffixIcon].
  final Widget? suffixWidget;

  /// Callback invoked when the user taps the suffix.
  /// 
  /// Called for both [suffixIcon] and [suffixWidget]; only relevant if one is supplied.
  final VoidCallback? onSuffixTap;

  // ...
}
  • prefixIcon and prefixWidget are mutually exclusive; supplying both triggers a debug assertion.
  • suffixIcon and suffixWidget are mutually exclusive; supplying both triggers a debug assertion.
  • onPrefixTap and onSuffixTap are distinct callbacks for their respective slots.

Help Affordance

An informational tooltip can be paired with the label or suffix, accessed via hover on desktop or long-press on mobile.

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// Title text displayed in the help tooltip.
  /// 
  /// If supplied, a help icon appears (location TBD; see open questions).
  final String? helpTitleText;

  /// Body text displayed in the help tooltip.
  /// 
  /// Shown below [helpTitleText] in the tooltip popup.
  final String? helpContentText;

  // ...
}
  • helpTitleText and helpContentText form a two-part help message.
  • Rendered as a tooltip component (wrapping the SDK's RawTooltip).

Interactivity

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// Callback invoked when the input value changes.
  final ValueChanged<String>? onChanged;

  /// Callback invoked when the user submits the input (e.g., via Enter on desktop).
  final ValueChanged<String>? onSubmit;

  /// Callback invoked when the user taps anywhere on the input field.
  /// 
  /// Distinct from [onPrefixTap] and [onSuffixTap]. Useful for picker-style inputs
  /// to open their selection surface.
  final VoidCallback? onTap;

  /// Whether the field is in read-only mode.
  /// 
  /// When true, the field displays a value but is not directly editable.
  /// Picker-style inputs use this to present a read-only display of the selected value.
  final bool readOnly;

  // ...
}
  • onChanged and onSubmit are independent callbacks.
  • onTap opens the picker surface for picker-style inputs.
  • readOnly — the field displays but is not editable. Used by picker-style inputs.

Focus and Lifecycle Management

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// Focus node for this input field.
  /// 
  /// If null, the widget constructs a node in [initState].
  /// The widget disposes only nodes it creates. Caller-supplied nodes are left untouched.
  final FocusNode? focusNode;

  /// Text controller for this input field.
  /// 
  /// If null, the widget constructs a controller in [initState].
  /// The widget disposes only controllers it creates. Caller-supplied controllers are left untouched.
  final TextEditingController? controller;

  // ...
}

Disposal contract: The widget disposes ONLY the instances it created; caller-supplied instances are left untouched. This is the standard pattern and prevents controller leaks, but bears explicit documentation because it is a common source of bugs.

Layout and Styling

// Design sketch — illustrative only
class LayrzTextInput extends StatefulWidget {
  /// Padding applied inside the input field.
  /// 
  /// Defaults to values from the M1 spacing tokens (see [design-tokens.md]).
  /// Can be overridden per-field to support custom layouts.
  final EdgeInsets? padding;

  // ...
}
  • padding uses spacing tokens from the design-tokens document by default (sp12, sp16, etc.).
  • Customizable per-field.

Value Display and Formatting

Concrete inputs compose LayrzTextInput and supply display text through the controller. Any visual affordance (color swatch, avatar thumbnail, icon preview) is rendered through the existing prefix widget parameter.

Each concrete input specifies:

  • The value type it holds (Color, DateTime, List<String>, etc.).
  • How to format that value for display in the controller's text.
  • The widget to render as a visual affordance in the prefix slot (if any).

What Each Concrete Input Adds

Every concrete Layrz*Input specifies only its deltas from the base contract:

  1. Value type — the data structure the input holds (e.g., Color, DateTime, Duration).
  2. Selection surface — the dialog or interaction pattern used to set the value (e.g., calendar picker, color wheel, emoji grid).
  3. Input-specific parameters — validation rules, formatting options, or constraints particular to the input type.

Illustrative Examples

Input Value Type Selection Surface Notes
LayrzNumberInput double Stepper buttons + keyboard input Min/max bounds, decimal formatting, step value
LayrzSelectInput String (or generic T) Searchable dropdown dialog Single selection, custom item rendering
LayrzMultiSelectInput List<String> (or generic List<T>) Searchable dropdown with checkboxes Multiple selection, checkboxes for visibility
LayrzCheckboxInput bool Inline checkbox, switch, or dropdown field Style variants: checkbox, switch, field (dropdown)
LayrzColorInput Color Color wheel and/or palette picker Hand-rolled to avoid Material dependency
LayrzFileInput {base64: String, bytes: Uint8List} System file browser Returns both base64 and raw bytes
LayrzIconInput IconData Icon selection dialog Picker over Solar icon set with search
LayrzAvatarInput String (base64) System image picker Stores image as base64, displays thumbnail

This table is illustrative of the pattern, not the complete catalogue. See Component Catalog for the full mapping of layrz_theme to layrz_ui inputs.


Shared Dependencies for the Entire Input Family

M1 Spacing Tokens

All default padding and inner spacing come from the M1 spacing token system. See the design-tokens document for token definitions (sp4, sp6, sp8, sp10, sp12, sp14, sp16, sp20, sp24, sp28, sp32, sp36, sp40, sp44, sp48).

Impact: Spacing is consistent across all inputs and customizable from the theme.

Tooltip Component

The help affordance is built on the tooltip component, which wraps the SDK's RawTooltip. This must be available for all inputs to render help text.

Dependency: LayrzTooltip (M2) must ship before inputs can use help affordances.

Material-Free TextSelectionControls

The SDK's EditableText requires a concrete TextSelectionControls implementation that handles text selection, copy/paste, and the selection toolbar. Material provides one, but layrz_ui cannot use it.

Blocker: A Material-free implementation must be provided. The SDK offers RawMagnifier and SystemContextMenu as building blocks for the mobile selection experience, but a complete desktop selection toolbar must be hand-rolled.

Impact: Without this, LayrzTextInput cannot be fully interactive. This blocks the entire input family.

Reference

See the flutter-347-audit document for the full analysis of SDK dependencies and Material coupling.


Design Reference Gap

The visual design of LayrzTextInput diverges from ThemedTextInput per internal meetings and is not yet captured in any linked design artefact.

Because LayrzTextInput is the architectural base for the entire input family, this design gap blocks the whole family from shipping, not just a single component.

Critical path item: A design reference (Figma, design spec, or annotated screenshot) must be attached to the LayrzTextInput component before implementation begins. This specification must cover:

  • Label positioning and typography
  • Placeholder styling and disappearance behavior
  • Prefix/suffix sizing and spacing
  • Focus and hover states
  • Error state appearance
  • Help affordance placement and interaction
  • Padding and field dimensions
  • Light and dark theme variants

Open Questions

The following items are genuinely unresolved. Answers must be decided before implementation of the input family can begin.

Validation and Error Display

  • Is there a validator callback parameter that returns error messages?
  • Does an equivalent of layrz_theme's FieldError component carry over, or is error display integrated directly into the field?
  • Are there multiple error display styles (inline text, colored border, icon, etc.)?

Keyboard and Input Constraints

  • Which standard EditableText passthroughs are exposed: keyboardType, textInputAction, inputFormatters, maxLength, autofocus, textCapitalization?
  • For multiline fields, is there a separate maxLines parameter?
  • Are there constraints on the number of lines displayed at once?

Disabled State

  • Is there a disabled state distinct from readOnly?
  • If yes, how is it signalled visually (grayed out, opacity, cursor not allowed)?
  • Does onTap fire when disabled?

Help Affordance Placement

  • Does the help affordance occupy the suffix slot or sit beside a caller-supplied suffix?
  • What happens when both are supplied (does help affordance move, or is an error raised)?
  • Is the help icon always visible, or does it only appear when a caller supplies help text?

Multiline Textarea Variant

  • Does the multiline textarea variant compose LayrzTextInput like the other inputs, or is it a separate sibling implementation?
  • If composed, are there distinct factory constructors (e.g., LayrzTextInput() for single-line, LayrzTextInput.multiline() for textarea)?

Combobox and Autocomplete

  • Do the combobox/autocomplete inputs (LayrzSelectInput, LayrzMultiSelectInput) compose LayrzTextInput too, given they need RawAutocomplete and an editable field rather than a read-only one?
  • Or are they separate implementations that reuse only the chrome layer?

Progress Tracking

Progress for input components and milestones is tracked in the GitHub Project for layrz_ui (Phase M3 for inputs, M4 for pickers), not in this documentation file. See the GitHub Project for current status, assignments, and blockers.

Documentation files record specification and architecture; the Project records progress.


Last updated: 2026-08-13
Related documents: Component Catalog, design-tokens, architecture, roadmap, decisions, flutter-347-audit

Clone this wiki locally