Creating Text Input Fields in Flutter
Text input fields are an essential part of Flutter applications because they allow users to enter information such as names, email addresses, passwords, phone numbers, search terms, comments, and messages. Flutter provides the TextField widget for general text input and TextFormField when the input needs to work with a Form and validation. :contentReference[oaicite:0]{index=0}
1. What Is a Text Input Field?
A text input field is a user-interface component where users can type or edit text. In Flutter, the most common widget for creating a text input field is TextField.
Text input fields are commonly used for:
- Username input
- Name and address forms
- Email addresses
- Phone numbers
- Passwords
- Search boxes
- Chat messages
- Comments and feedback
- Product search
- Registration and login forms
2. Basic TextField Syntax
TextField(
decoration: InputDecoration(
labelText: 'Enter your name',
),
)
The TextField accepts keyboard input and can be customized using properties such as controller, keyboardType, obscureText, maxLines, onChanged, and onSubmitted. :contentReference[oaicite:1]{index=1}
3. Complete Basic Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Text Input Example'),
),
body: const Padding(
padding: EdgeInsets.all(20),
child: TextField(
decoration: InputDecoration(
labelText: 'Name',
hintText: 'Enter your name',
border: OutlineInputBorder(),
),
),
),
),
);
}
}
4. Understanding InputDecoration
InputDecoration controls the visual appearance of a Material text input field. It can provide labels, hints, icons, helper text, error text, borders, prefixes, suffixes, and other styling options. :contentReference[oaicite:2]{index=2}
Common Properties
| Property | Purpose |
|---|
labelText | Displays a label for the input. |
hintText | Displays an example or instruction. |
helperText | Displays additional information below the field. |
errorText | Displays an error message. |
prefixIcon | Displays an icon before the input. |
suffixIcon | Displays an icon after the input. |
prefixText | Displays text before the entered value. |
suffixText | Displays text after the entered value. |
border | Defines the default border. |
focusedBorder | Defines the border when the field is focused. |
enabledBorder | Defines the border when the field is enabled but not focused. |
filled | Determines whether the background is filled. |
fillColor | Defines the background color. |
5. Creating a TextField with a Label
TextField(
decoration: InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
)
The label identifies what information the user should enter.
6. Creating a TextField with Hint Text
TextField(
decoration: InputDecoration(
hintText: 'Enter your full name',
border: OutlineInputBorder(),
),
)
The hint provides an example or instruction inside the input area.
7. Label and Hint Together
TextField(
decoration: InputDecoration(
labelText: 'Email Address',
hintText: '[email protected]',
border: OutlineInputBorder(),
),
)
8. Adding Icons to Text Input Fields
Prefix Icon
TextField(
decoration: InputDecoration(
labelText: 'Username',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
)
Suffix Icon
TextField(
decoration: InputDecoration(
labelText: 'Search',
suffixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
)
Prefix and Suffix Together
TextField(
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
suffixIcon: Icon(Icons.check),
border: OutlineInputBorder(),
),
)
9. Different Border Styles
Outline Border
TextField(
decoration: InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
)
Rounded Outline Border
TextField(
decoration: InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
Underline Border
TextField(
decoration: InputDecoration(
labelText: 'Name',
border: UnderlineInputBorder(),
),
)
10. Filled Text Input Field
TextField(
decoration: InputDecoration(
labelText: 'Username',
filled: true,
fillColor: Colors.grey,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
)
11. TextEditingController
TextEditingController allows the application to read and control the text entered into a TextField. It can also be used to set an initial value and observe changes. When a controller is created by a StatefulWidget, it should be disposed when it is no longer needed. :contentReference[oaicite:3]{index=3}
class NamePage extends StatefulWidget {
const NamePage({super.key});
@override
State createState() => _NamePageState();
}
class _NamePageState extends State {
final nameController = TextEditingController();
@override
void dispose() {
nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
),
),
);
}
}
12. Retrieving Input from TextField
Use the controller's text property to retrieve the current value.
String name = nameController.text;
Example
ElevatedButton(
onPressed: () {
print(nameController.text);
},
child: const Text('Get Name'),
)
13. Setting Initial Text
final nameController = TextEditingController(
text: 'John Doe',
);
Then connect it to the TextField:
TextField(
controller: nameController,
)
14. Detecting Text Changes with onChanged
The onChanged callback runs whenever the user changes the text. It is useful for live search, character counters, filtering, and dynamic UI updates. :contentReference[oaicite:4]{index=4}
TextField(
onChanged: (value) {
print('Entered text: $value');
},
decoration: InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(),
),
)
Live Character Counter
class CharacterCounter extends StatefulWidget {
const CharacterCounter({super.key});
@override
State createState() => _CharacterCounterState();
}
class _CharacterCounterState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (value) {
setState(() {
count = value.length;
});
},
decoration: const InputDecoration(
labelText: 'Message',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
Text('Characters: $count'),
],
);
}
}
15. Handling Submitted Text
The onSubmitted callback runs when the user submits the field, commonly by using the action button on the keyboard. :contentReference[oaicite:5]{index=5}
TextField(
onSubmitted: (value) {
print('Submitted: $value');
},
decoration: const InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(),
),
)
16. Creating an Email Input Field
For email input, use TextInputType.emailAddress so the platform can provide an appropriate keyboard layout.
TextField(
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email Address',
hintText: '[email protected]',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
)
17. Creating a Phone Number Input Field
TextField(
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone Number',
prefixIcon: Icon(Icons.phone),
border: OutlineInputBorder(),
),
)
18. Creating a Number Input Field
TextField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Age',
prefixIcon: Icon(Icons.numbers),
border: OutlineInputBorder(),
),
)
19. Creating a Password Input Field
Use obscureText: true when the entered characters should be hidden, such as in a password field. :contentReference[oaicite:6]{index=6}
TextField(
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
)
20. Password Visibility Toggle
class PasswordField extends StatefulWidget {
const PasswordField({super.key});
@override
State createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State {
bool obscurePassword = true;
@override
Widget build(BuildContext context) {
return TextField(
obscureText: obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
obscurePassword = !obscurePassword;
});
},
),
border: const OutlineInputBorder(),
),
);
}
}
21. Creating a Multiline Text Input Field
Use maxLines to allow the user to enter multiple lines of text.
TextField(
maxLines: 5,
decoration: const InputDecoration(
labelText: 'Description',
hintText: 'Write your description',
border: OutlineInputBorder(),
),
)
Common Uses
- Comments
- Feedback
- Address
- Messages
- Descriptions
- Notes
22. Limiting Input Length
The maxLength property can limit the number of characters entered.
TextField(
maxLength: 20,
decoration: const InputDecoration(
labelText: 'Username',
border: OutlineInputBorder(),
),
)
23. Text Capitalization
The textCapitalization property controls automatic capitalization behavior.
TextField(
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
)
Common options include:
TextCapitalization.none
TextCapitalization.characters
TextCapitalization.words
TextCapitalization.sentences
24. Text Alignment
TextField(
textAlign: TextAlign.center,
decoration: const InputDecoration(
labelText: 'OTP',
border: OutlineInputBorder(),
),
)
Common alignment values include TextAlign.start, TextAlign.center, and TextAlign.end.
25. Read-Only Text Input
Use readOnly: true when users should be able to view or select the content but should not edit it.
TextField(
readOnly: true,
controller: TextEditingController(
text: 'User ID: 1001',
),
decoration: const InputDecoration(
labelText: 'User ID',
border: OutlineInputBorder(),
),
)
26. Disabled Text Input Field
Set enabled: false when the field should not accept interaction.
TextField(
enabled: false,
decoration: const InputDecoration(
labelText: 'Disabled Field',
border: OutlineInputBorder(),
),
)
27. Autofocus
The autofocus property can request focus when the TextField first becomes visible. This can be useful for search screens and similar interfaces. :contentReference[oaicite:7]{index=7}
TextField(
autofocus: true,
decoration: const InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(),
),
)
28. FocusNode
A FocusNode can be used when an application needs programmatic control over which text field has keyboard focus. Focus nodes are long-lived objects and should have their lifecycle managed appropriately. :contentReference[oaicite:8]{index=8}
class FocusExample extends StatefulWidget {
const FocusExample({super.key});
@override
State createState() => _FocusExampleState();
}
class _FocusExampleState extends State {
final nameFocus = FocusNode();
@override
void dispose() {
nameFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(
focusNode: nameFocus,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
);
}
}
29. Moving Focus Between Fields
Focus can be moved from one TextField to another, which is useful for login and registration forms.
class LoginFields extends StatefulWidget {
const LoginFields({super.key});
@override
State createState() => _LoginFieldsState();
}
class _LoginFieldsState extends State {
final emailFocus = FocusNode();
final passwordFocus = FocusNode();
@override
void dispose() {
emailFocus.dispose();
passwordFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
focusNode: emailFocus,
textInputAction: TextInputAction.next,
onSubmitted: (_) {
FocusScope.of(context).requestFocus(passwordFocus);
},
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
focusNode: passwordFocus,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
),
],
);
}
}
30. Hiding the Keyboard
Keyboard focus can be removed using FocusScope.of(context).unfocus().
ElevatedButton(
onPressed: () {
FocusScope.of(context).unfocus();
},
child: const Text('Hide Keyboard'),
)
31. Input Formatters
Input formatters can be used to restrict or transform user input. They are useful when an input must follow a specific format.
Import the Flutter services package:
import 'package:flutter/services.dart';
Digits Only
TextField(
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
labelText: 'Enter Number',
border: OutlineInputBorder(),
),
)
Maximum Input Length
TextField(
inputFormatters: [
LengthLimitingTextInputFormatter(10),
],
decoration: const InputDecoration(
labelText: 'Input',
border: OutlineInputBorder(),
),
)
32. TextInputAction
textInputAction controls the action button displayed by the software keyboard.
TextField(
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
)
Other commonly used values include:
TextInputAction.next
TextInputAction.done
TextInputAction.search
TextInputAction.go
TextInputAction.send
33. Styling Focused and Enabled Borders
TextField(
decoration: InputDecoration(
labelText: 'Email',
border: const OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
)
34. Helper Text
Helper text can provide additional instructions to the user.
TextField(
decoration: const InputDecoration(
labelText: 'Username',
helperText: 'Use 5 to 20 characters',
border: OutlineInputBorder(),
),
)
35. Error Text
errorText can be used to display an input error message.
TextField(
decoration: const InputDecoration(
labelText: 'Email',
errorText: 'Please enter a valid email',
border: OutlineInputBorder(),
),
)
36. TextField and TextFormField
Flutter provides both TextField and TextFormField. A TextField is commonly used for standalone text input, while TextFormField wraps a TextField and integrates it with a surrounding Form, providing features such as validation. :contentReference[oaicite:9]{index=9}
| Feature | TextField | TextFormField |
|---|
| Basic text input | Yes | Yes |
| InputDecoration | Yes | Yes |
| Controller | Yes | Yes |
| Form integration | No direct FormField integration | Yes |
| Validator | Not directly provided as a FormField | Yes |
| onChanged | Yes | Yes |
| Password input | Yes | Yes |
| Multiline input | Yes | Yes |
37. Creating a Form with TextFormField
For forms that need validation, create a Form, provide a GlobalKey, and add TextFormField widgets with validator functions. Flutter's official form recipe follows this pattern. :contentReference[oaicite:10]{index=10}
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State createState() => _LoginFormState();
}
class _LoginFormState extends State {
final formKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
print('Form is valid');
}
},
child: const Text('Submit'),
),
],
),
);
}
}
38. Complete Registration Text Input Example
import 'package:flutter/material.dart';
class RegistrationPage extends StatefulWidget {
const RegistrationPage({super.key});
@override
State createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State {
final nameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final passwordController = TextEditingController();
@override
void dispose() {
nameController.dispose();
emailController.dispose();
phoneController.dispose();
passwordController.dispose();
super.dispose();
}
void register() {
final name = nameController.text.trim();
final email = emailController.text.trim();
final phone = phoneController.text.trim();
final password = passwordController.text;
if (name.isEmpty ||
email.isEmpty ||
phone.isEmpty ||
password.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please fill all fields'),
),
);
return;
}
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Registration successful'),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Registration'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
controller: nameController,
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Full Name',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email Address',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone Number',
prefixIcon: Icon(Icons.phone),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: register,
child: const Text('Register'),
),
),
],
),
),
);
}
}
39. Creating a Search Input Field
TextField(
textInputAction: TextInputAction.search,
onSubmitted: (value) {
print('Search: $value');
},
decoration: const InputDecoration(
hintText: 'Search products...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
)
Search fields can also use onChanged to update results while the user types. Flutter's documentation specifically describes this pattern for search and autocomplete experiences. :contentReference[oaicite:11]{index=11}
40. Creating a Chat Message Input
class MessageInput extends StatefulWidget {
const MessageInput({super.key});
@override
State createState() => _MessageInputState();
}
class _MessageInputState extends State {
final messageController = TextEditingController();
@override
void dispose() {
messageController.dispose();
super.dispose();
}
void sendMessage() {
final message = messageController.text.trim();
if (message.isEmpty) {
return;
}
print('Message: $message');
messageController.clear();
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: TextField(
controller: messageController,
textInputAction: TextInputAction.send,
decoration: const InputDecoration(
hintText: 'Type a message...',
border: OutlineInputBorder(),
),
onSubmitted: (_) {
sendMessage();
},
),
),
IconButton(
onPressed: sendMessage,
icon: const Icon(Icons.send),
),
],
);
}
}
41. Important TextField Properties
| Property | Description |
|---|
controller | Controls and retrieves the entered text. |
decoration | Customizes labels, hints, borders, icons, and errors. |
keyboardType | Specifies the expected keyboard/input type. |
textInputAction | Controls the keyboard action button. |
obscureText | Hides entered characters. |
maxLines | Controls the maximum number of lines. |
minLines | Controls the minimum number of lines. |
maxLength | Limits the number of characters. |
onChanged | Runs whenever the text changes. |
onSubmitted | Runs when the user submits the field. |
focusNode | Controls and monitors focus. |
autofocus | Requests focus when the field appears. |
readOnly | Prevents editing while keeping the field interactive. |
enabled | Enables or disables the input field. |
inputFormatters | Restricts or transforms input. |
textCapitalization | Controls automatic text capitalization. |
textAlign | Controls horizontal text alignment. |
42. Best Practices for Creating Text Input Fields
- Use a meaningful label for every important input field.
- Use hint text when an example will help the user.
- Choose an appropriate keyboard type such as email, phone, or number.
- Use
TextEditingController when you need to read or modify input programmatically.
- Dispose controllers when they are no longer needed. :contentReference[oaicite:12]{index=12}
- Use
obscureText for password input.
- Use
TextFormField and Form when input validation is required.
- Use appropriate input formatters for restricted input.
- Use multiline fields for descriptions and comments.
- Use
FocusNode when the form requires custom focus management.
- Make text fields sufficiently large and clear for comfortable interaction.
- Use meaningful error messages instead of generic messages such as "Invalid input".
43. Common Mistakes
Mistake 1: Creating Controllers Inside build()
Avoid repeatedly creating controllers inside the build() method.
Mistake 2: Not Disposing Controllers
@override
void dispose() {
nameController.dispose();
super.dispose();
}
Mistake 3: Using the Wrong Keyboard Type
For example, an email field should normally use:
keyboardType: TextInputType.emailAddress
Mistake 4: Forgetting Password Obscuring
TextField(
obscureText: true,
)
Mistake 5: Not Handling Keyboard Overflow
Forms containing many input fields may need a scrolling parent such as SingleChildScrollView so that fields remain accessible when the keyboard appears.
44. Practical Exercise
Create a Flutter registration screen containing the following text input fields:
- Full Name
- Email Address
- Phone Number
- Password
- Confirm Password
- Address
- Short Bio
Requirements:
- Use appropriate labels and hint text.
- Add prefix icons.
- Use
TextEditingController.
- Use appropriate keyboard types.
- Hide password characters.
- Use multiline input for Address and Bio.
- Limit the Bio to a suitable number of characters.
- Add a Register button.
- Display a success message after submission.
45. Interview Questions
- What is TextField in Flutter?
- How do you create a basic text input field?
- What is InputDecoration?
- What is the difference between hintText and labelText?
- What is TextEditingController?
- Why should TextEditingController be disposed?
- How do you retrieve text from a TextField?
- What is the purpose of onChanged?
- What is the difference between onChanged and onSubmitted?
- How do you create a password field?
- How do you create a multiline text input?
- How can you limit the number of characters?
- What is keyboardType?
- What is FocusNode?
- How can you move focus between two TextFields?
- What is TextFormField?
- What is the difference between TextField and TextFormField?
- How can you validate text input in Flutter?
- How can inputFormatters be used?
- How can you customize a TextField border?
46. Quick Revision
| Concept | Example |
|---|
| Basic input | TextField() |
| Label | labelText: 'Name' |
| Hint | hintText: 'Enter name' |
| Controller | controller: nameController |
| Read value | controller.text |
| Email input | TextInputType.emailAddress |
| Phone input | TextInputType.phone |
| Password | obscureText: true |
| Multiline | maxLines: 5 |
| Character limit | maxLength: 20 |
| Input changes | onChanged |
| Submit | onSubmitted |
| Focus | FocusNode |
| Form validation | TextFormField |
47. Key Takeaways
TextField is the primary Flutter widget for creating text input fields.
InputDecoration controls the appearance of the field.
TextEditingController provides programmatic access to entered text.
onChanged is useful for responding to input changes.
onSubmitted is useful for handling keyboard submission.
keyboardType helps provide an appropriate input keyboard.
obscureText is commonly used for passwords.
maxLines allows multiline text input.
inputFormatters can restrict or transform input.
FocusNode provides programmatic focus control.
TextFormField is useful for forms that require validation. :contentReference[oaicite:13]{index=13}
48. Official Flutter Resources
49. JustAcademy Flutter Resources