WInput
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 (fixes #102).
On this page
forms/input_basic
The live demo is not available for this example.
input_basic.dart
View source
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.
WInput(
placeholder: 'Type something...',
className: 'w-full p-2 border rounded',
onChanged: (val) => print(val),
)
Constructor#
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,
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 behavior (text, password, email, number, multiline). number restricts input to a signed decimal on every platform (web included); 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.) |
textCapitalization |
TextCapitalization |
TextCapitalization.none |
Auto-capitalize behavior (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#
Multiline Input#
For text areas, set the type to InputType.multiline. You can control the height using minLines or standard sizing classes.
widgets/w_input_multiline
The live demo is not available for this example.
w_input_multiline.dart
View source
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.
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.
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 toTextInputAction.doneand a multiline one to.newline. Passing.nextis a deliberate opt-in, because Flutter implements it asfocusNode.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.nextonly 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.
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, dusk 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.
widgets/w_input_search
The live demo is not available for this example.
w_input_search.dart
View source
WInput(
prefix: Icon(Icons.search, color: Colors.gray),
className: 'pl-10 p-2 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.
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, uppercase |
| Colors | bg-{color}, text-{color} |
| Borders | border, border-{n}, rounded-{size}, border-{color} |
| Effects | shadow-{size}, opacity-{n}, ring-{n}, ring-offset-{n} |
Customizing Theme#
You can override the default input appearance through WindThemeData. This is useful for setting global border colors or padding defaults.
WindThemeData(
colors: {
'input-border': Colors.blueGrey,
},
// WInput respects global spacing units for p-x classes
baseSpacingUnit: 4.0,
)
Related Documentation#
- WFormInput - Form-integrated input with labels and validation.
- WButton - Interactive button component.
- WText - Typography component.