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,
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) |
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...',
)
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(),
)
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.