TextField
Flutter TextField: controllers, focus, decoration, validation, and the patterns for clean forms.
Flutter — TextField
EXAMPLE
import 'package:flutter/material.dart';
# ===== Basic =====
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'you@example.com',
border: OutlineInputBorder(),
),
onChanged: (value) => print(value),
);
# ===== Controller (recommended for non-trivial forms) =====
class _FormState extends State<MyForm> {
final _emailCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
@override
void dispose() {
_emailCtrl.dispose();
_passwordCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: [
TextField(
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordCtrl,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
),
ElevatedButton(
onPressed: () => print('email: ${_emailCtrl.text}'),
child: const Text('Sign in'),
),
]);
}
}
# ===== Focus =====
final focusNode = FocusNode();
TextField(focusNode: focusNode, ...);
# Focus next field on submit:
TextField(
textInputAction: TextInputAction.next,
onSubmitted: (_) => FocusScope.of(context).requestFocus(passwordFocus),
);
# Auto-focus on mount:
TextField(autofocus: true)
# ===== Form + validation =====
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: (v) {
if (v == null || !v.contains('@')) return 'invalid email';
return null;
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (v) => v != null && v.length >= 8 ? null : 'min 8 chars',
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// submit
}
},
child: const Text('Sign in'),
),
]),
);
# ===== InputDecoration tour =====
InputDecoration(
labelText: 'Email',
hintText: 'you@example.com',
helperText: 'We never share this',
errorText: hasError ? 'Required' : null,
prefixIcon: const Icon(Icons.email),
suffixIcon: IconButton(icon: const Icon(Icons.clear), onPressed: () => _emailCtrl.clear()),
border: const OutlineInputBorder(),
filled: true,
fillColor: Colors.grey.shade100,
)
# ===== Keyboard types =====
keyboardType: TextInputType.emailAddress
keyboardType: TextInputType.number
keyboardType: TextInputType.phone
keyboardType: TextInputType.url
keyboardType: TextInputType.multiline,
maxLines: 5,
# ===== Input formatters =====
import 'package:flutter/services.dart';
TextField(
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
],
);
# ===== Patterns to internalise =====
// - TextEditingController for non-trivial fields; dispose() in State.dispose()
// - TextFormField + Form for validation flows
// - textInputAction + FocusNode chain for keyboard 'Next' / 'Done'
// - inputFormatters for masks (phone, credit card, money)
# ===== Pitfalls =====
// - Forgetting to dispose controllers -> memory leaks
// - Reading TextField.value vs controller.text inconsistently
// - autocorrect / enableSuggestions left on for passwords (leaks)
// - Tight Stack + Keyboard insets without resizeToAvoidBottomInset
Why it matters
TextField + TextEditingController + InputDecoration + Form. Dispose controllers, validate with TextFormField, chain focus with textInputAction, and reach for inputFormatters when input needs masks. The patterns are small; the cleanliness pays off in every form screen.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
TextField(
decoration: const InputDecoration(border: OutlineInputBorder(), labelText: 'Name'),
onChanged: (v) => print(v),
)
Try it Yourself »
Discussion
Loading…