Skip to content

WInput

View raw .md

A utility-first form input that combines React-style controlled state management with Tailwind-like styling. It renders Material-free (EditableText core inside a Container) and works under any theming ancestor: Material, Cupertino, a custom app, or a bare WidgetsApp. No Material ancestor required.

On this page
Preview forms/input_basic A live demo of this example follows. input_basic.dart View source Open demo
dart
WInput(
  value: _email,
  onChanged: (value) => setState(() => _email = value),
  type: InputType.email,
  placeholder: 'Enter your email',
  className: 'p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500',
  placeholderClassName: 'text-gray-400 italic',
)

Basic Usage#

Pass value and onChanged for controlled (React-style) binding. Omit both to let the field manage its own state internally. WInput works under any ancestor widget tree. No MaterialApp required.

dart
WInput(
  placeholder: 'Type something...',
  className: 'w-full p-2 border rounded',
  onChanged: (val) => print(val),
)

Constructor#

dart
const WInput({
  Key? key,
  String? value,
  ValueChanged<String>? onChanged,
  InputType type = InputType.text,
  String? className,
  String? placeholderClassName,
  String? placeholder,
  bool enabled = true,
  bool readOnly = false,
  bool autofocus = false,
  TextInputAction? textInputAction,
  Iterable<String>? autofillHints = const <String>[],
  ValueChanged<String>? onSubmitted,
  VoidCallback? onEditingComplete,
  VoidCallback? onTap,
  TapRegionCallback? onTapOutside,
  int? maxLines,
  int minLines = 1,
  FocusNode? focusNode,
  TextEditingController? controller,
  Set<String>? states,
  List<TextInputFormatter>? inputFormatters,
  String decimalSeparator = '.',
  TextCapitalization textCapitalization = TextCapitalization.none,
  bool autocorrect = true,
  bool enableSuggestions = true,
  Widget? prefix,
  Widget? suffix,
  String? semanticLabel,
})

Props#

Prop Type Default Description
className String? null Wind utility classes for the input field
placeholderClassName String? null Utility classes for styling the hint text
value String? null The controlled value of the input
onChanged ValueChanged<String>? null Callback when text changes
type InputType InputType.text Input keyboard and behaviour (text, password, email, number, multiline). number restricts input to a signed decimal on every platform (web included), with decimalSeparator as its one decimal mark; pass inputFormatters to override.
placeholder String? null Hint text shown when input is empty
enabled bool true Whether the input is interactive
readOnly bool false Whether the input is read-only
autofocus bool false Autofocus on mount
textInputAction TextInputAction? null Keyboard action (e.g., .done, .next). When null, resolves to .done on a single-line field and .newline on a multiline one.
autofillHints Iterable<String>? const [] Hints for a password manager or the platform autofill service (e.g., [AutofillHints.email]). The empty default keeps platform autofill on with no hint; null opts the field out. Fields of one form go inside an AutofillGroup, which the host provides; WInput never creates one.
onSubmitted ValueChanged<String>? null Callback when action button is pressed
onTapOutside TapRegionCallback? null Callback when tapping outside (useful for blur)
maxLines int? null Max lines for multiline input
minLines int 1 Min lines for multiline input
prefix Widget? null Widget displayed before text
suffix Widget? null Widget displayed after text
controller TextEditingController? null Optional external controller
states Set<String>? null Custom states for dynamic styling
onEditingComplete VoidCallback? null Callback fired when the user finishes editing (loses focus, presses done, etc.)
onTap VoidCallback? null Callback fired when the field is tapped
focusNode FocusNode? null External focus controller for programmatic focus management
inputFormatters List<TextInputFormatter>? null Formatters applied as the user types (digit-only, masks, etc.)
decimalSeparator String '.' The one decimal mark a number field accepts. Pass the locale's mark (',' in Turkish or German) so 2,5 is not typed as 25; the text stays as typed, so convert it to . before it goes on the wire. One character, never a digit, + or - (debug asserts). Ignored when inputFormatters is set.
textCapitalization TextCapitalization TextCapitalization.none Auto-capitalise behaviour (none / sentences / words / characters)
autocorrect bool true Enable OS autocorrect suggestions
enableSuggestions bool true Enable OS suggestion bar (Android)
semanticLabel String? null Accessible name for the single textbox semantics node. Falls back to placeholder when null. Used by screen readers and getByLabel-style locators.

Layout Modes#

A single-line field centres its text and placeholder vertically in whatever height min-h-* or h-* gives the box, as a CSS input does; a multiline field keeps its text at the top.

Multiline Input#

For text areas, set the type to InputType.multiline. You can control the height using minLines or standard sizing classes.

Preview widgets/w_input_multiline A live demo of this example follows. w_input_multiline.dart View source Open demo
dart
WInput(
  type: InputType.multiline,
  minLines: 3,
  maxLines: 5,
  className: 'w-full p-4 border rounded-xl bg-gray-50',
  placeholder: 'Enter your message...',
)

Keyboard Clearance#

A focused WInput lifts the WHOLE field above the software keyboard, not just the line the caret is on.

Flutter reveals the caret rect with scrollPadding around it, which is the same box as the field while the field is one line tall. On a multiline field it is not: the caret sits on the first line, so line one clears the keyboard and every line below it stays under it. WInput widens scrollPadding by however much of the field sits below the caret, so Flutter's own caret scroll carries the rest of the field with it, and it recomputes as the caret moves down through the text.

The keyboard is not the only thing in the way. A WKeyboardActions toolbar is drawn on top of the keyboard and the engine reports nothing about it, so the region a field has to clear is the keyboard plus the bar. WInput reads the bar's published height and adds it, which means a field inside a WKeyboardActions needs no setup at all.

dart
WInput(
  type: InputType.multiline,
  minLines: 3,
  maxLines: 5,
  className: 'w-full p-4 border rounded-xl',
  placeholder: 'Write an update...',
)

Event Handling#

WInput provides standard callbacks for user interaction. Use onSubmitted for handling "Enter" or keyboard action buttons.

dart
WInput(
  className: 'border p-2',
  textInputAction: TextInputAction.search,
  onSubmitted: (value) {
    _performSearch(value);
  },
  onTapOutside: (_) => FocusScope.of(context).unfocus(),
)

The Return key closes the keyboard by default. With no textInputAction, a single-line field resolves to TextInputAction.done and a multiline one to .newline. Passing .next is a deliberate opt-in, because Flutter implements it as focusNode.nextFocus(): the next FOCUSABLE widget, not the next text field. On a form whose traversal order runs through buttons, colour swatches and switches, the same Return key does two different things on one screen, dismissing the keyboard where the next focusable is a button and jumping past several fields where it happens to be editable. Reach for .next only where you know the following focusable IS the next input, which is a fact only the form itself has. For a toolbar that advances through a known field order instead, see WKeyboardActions.

State Variants#

Wind automatically manages focus:, disabled: (when enabled: false), and readonly: (when readOnly: true) states. You can also trigger custom states like error: by passing them to the states property.

dart
WInput(
  states: _hasError ? {'error'} : {},
  className: 'border border-gray-300 focus:border-blue-500 error:border-red-500',
)

Accessibility#

WInput emits exactly one typeable textbox semantics node. Its accessible name is semanticLabel when provided, falling back to placeholder. This single-node shape ensures label-by-text locators (e.g., Playwright getByLabel, tapir label lookup) resolve cleanly with no duplicate accessible names.

InputType.password fields report isObscured: true in the semantics tree so screen readers announce the field as a password input.

When the field is enabled: false, it is non-editable and reports isReadOnly in the semantics tree.

Styling Examples#

Search Input with Prefix#

You can use the prefix prop to add icons while maintaining the utility-first styling of the input.

Preview widgets/w_input_search A live demo of this example follows. w_input_search.dart View source Open demo
dart
WInput(
  prefix: Icon(Icons.search, color: Colors.grey),
  className: 'p-2 pl-10 bg-gray-100 rounded-full border-transparent focus:bg-white focus:border-blue-500',
  placeholder: 'Search...',
)

Password Field#

Use InputType.password to obscure text. You can use the suffix prop to add a visibility toggle.

dart
WInput(
  type: _obscure ? InputType.password : InputType.text,
  suffix: IconButton(
    icon: Icon(_obscure ? Icons.visibility : Icons.visibility_off),
    onPressed: () => setState(() => _obscure = !_obscure),
  ),
  className: 'p-3 border rounded',
)

All Supported Classes#

Category Classes
Layout w-{size}, h-{size}, flex-1, w-full
Spacing p-{n}, px-{n}, py-{n}, m-{n}
Typography text-{size}, font-{weight}, italic
Colours bg-{color}, text-{color}
Borders border, border-{n}, rounded-{size}, border-{color}
Effects shadow-{size}, ring-{n}, ring-offset-{n}

Customizing Theme#

WInput draws its default border in a fixed colour that the theme does not control. Set a border colour on each field with border-{color} and its dark: pair in className. The theme does scale the p-* spacing classes through baseSpacingUnit:

dart
WindThemeData(
  // WInput respects global spacing units for p-x classes
  baseSpacingUnit: 4.0,
)
  • WFormInput - Form-integrated input with labels and validation.
  • WButton - Interactive button component.
  • WText - Typography component.
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.