Handling User Input in Flutter: TextField, Forms and Validation for Beginners (2026)

Every real Flutter app takes input from the user. Here’s the right way to handle it from day one.

Login screens, signup pages, search boxes, profile editors, checkout forms — they all depend on the same two widgets: TextField and TextFormField. Flutter makes basic text input easy, but validation — catching empty fields, wrong formats, and short passwords before they reach your server — requires a specific pattern using Form, GlobalKey, and validator().

This guide covers everything a beginner needs: when to use TextField vs TextFormField, how the Form + GlobalKey validation flow works, how to pass data using controllers, how to validate while typing, and the mistakes that trip up almost every beginner. Every section has a complete, copy-paste ready code example.

Prerequisites: You should be comfortable with StatefulWidget and setState(). If not, read our Stateless vs Stateful Widgets guide first — all the form examples here use StatefulWidget.

Table of Contents


1. TextField vs TextFormField: Which to Use

Flutter gives you two text input widgets. They look identical on screen but serve different purposes:

Feature TextField TextFormField
Basic text input ✅ Yes ✅ Yes
Works inside a Form ❌ No ✅ Yes
Built-in validator() support ❌ No ✅ Yes
Inline error messages Manual only ✅ Automatic
TextEditingController ✅ Yes ✅ Yes
Best for Search boxes, chat input, live previews Login, signup, checkout, any validated form
💡 Simple rule: If you have a Submit button that needs to validate one or more fields before proceeding — use TextFormField inside a Form. For everything else (search, chat, live preview), TextField with a controller is simpler and cleaner.

2. Basic TextField with a Controller

Before getting into forms, it’s worth understanding TextField and TextEditingController on their own. A controller lets you read the current value, set it programmatically, clear it, and listen for every change.

Controller method What it does When to use
_controller.text Read current value On submit button press
_controller.text = 'value' Set/prefill value Loading saved data into a field
_controller.clear() Empty the field After successful submit, reset
_controller.addListener(fn) Fire callback on every change Live search, character counters
_controller.dispose() Free memory when widget removes Always — in dispose() override

3. The Form + GlobalKey Validation Pattern

This is the official Flutter pattern for form validation, and understanding it once means you can build any validated form. There are four moving parts:

Part What it is Role in the pattern
Form Widget Container that groups all fields; owns the validation state
GlobalKey<FormState> Key object Unique handle to access the Form’s state from your code
TextFormField Widget Individual field with a validator callback
_formKey.currentState!.validate() Method call Triggers all validators; returns true if all pass
💡 The validator contract: Return a String → field is invalid, the string is shown as the error message below the field. Return null → field is valid, no error shown. That’s the entire API.

4. Real Example: Login Form with Email & Password Validation

The most common beginner form — two fields, two validators, one submit button. Notice how each field validates independently, and validate() checks both at once when the button is pressed.

💡 Two additions beyond the basics: obscureText: !_passwordVisible hides the password by default, and the suffixIcon eye button toggles it. This is the standard pattern used in almost every real login screen.

5. Real Example: Signup Form with 4 Fields

A signup form adds two new challenges: confirming the password matches, and cross-field validation (one field referencing another). Notice the validator on the confirm password field reads from the password controller.

⚠️ Notice: The confirm password validator reads _passwordController.text directly. This is normal — validators are closures and can access anything in scope. Cross-field validation is this simple in Flutter; no special API needed.

6. Validating While Typing with autovalidateMode

By default, validators only fire when you call validate(). If you want errors to appear as the user types — common in signup screens — use autovalidateMode. The best beginner-friendly option is AutovalidateMode.onUserInteraction, which waits until the user has touched the field before starting validation.

autovalidateMode value When validators fire Best for
disabled (default) Only on validate() call Login forms (validate on submit)
onUserInteraction After first interaction with that field Signup forms (live feedback per field)
always Immediately on every rebuild Avoid — shows errors before user types anything

7. Controller Tricks: Prefill, Clear, and Listen

A common real-world scenario: the user opens an “Edit Profile” screen and the fields are pre-populated with their existing data. You can prefill a field in initState():


8. InputDecoration: Making Fields Look Good

Both TextField and TextFormField share the same InputDecoration class for styling. Here’s a reference of the properties beginners use most:

Property What it shows Goes away when
labelText Floating label above field Never — floats up when focused
hintText Placeholder inside field When user starts typing
helperText Info text below field Replaced by error text when invalid
errorText Red error below field When set to null
prefixIcon Icon on left inside field Never
suffixIcon Icon/button on right inside field Never (or conditionally)

9. Common Beginner Mistakes

Mistake 1: Using TextField and looking for validator

Mistake 2: Forgetting to assign key: to Form

Mistake 3: Returning false instead of a String from validator

Mistake 4: Forgetting to dispose controllers

Mistake 5: Calling validate() before the Form is built


10. Interview Q&A

Q1: What is the difference between TextField and TextFormField?

Answer: TextField is the basic text input widget — it captures text and exposes it via a controller or onChanged, but has no built-in validation support. TextFormField is a wrapper around TextField that integrates with the Form widget, adding a validator callback that can display error messages and participate in the form’s validate() flow.

Q2: What does validator return, and what does each return value mean?

Answer: The validator function returns String?. If the field value is invalid, return a non-null String — that string is displayed as the error message below the field. If the value is valid, return null. Returning null means valid; returning any string means invalid.

Q3: Why do we use GlobalKey<FormState>?

Answer: The Form widget holds state (validity of all its child fields), but that state lives inside the widget tree. A GlobalKey<FormState> gives you a stable handle to access that state from anywhere in your code — typically to call validate() when a submit button is pressed, which triggers all field validators at once and returns true only if every field passes.

Q4: What does validate() do exactly?

Answer: It iterates over every TextFormField inside the Form, calls each field’s validator with the current value, and shows the returned error string below each field that fails. It returns true if every validator returned null (all fields valid), and false if any returned a non-null string.

Q5: When should you use TextEditingController?

Answer: Use a TextEditingController when you need to read the field’s value after the user types (e.g. on submit), prefill the field with existing data, clear the field programmatically, or listen to every text change. Always create it in the widget’s state, and always call dispose() on it in the dispose() method to prevent memory leaks.

Q6: How do you validate while typing instead of only on submit?

Answer: Set autovalidateMode: AutovalidateMode.onUserInteraction on the TextFormField or on the Form widget. This triggers the validator after the user first interacts with the field, giving live feedback as they type without showing errors before they’ve touched anything. Avoid AutovalidateMode.always — it shows errors immediately on load before the user has had a chance to type.

See Forms in a Real App

Forms and controllers are used throughout the notes app series — the note editor uses a TextEditingController to read the title and body before saving. See the full pattern in context:

Article Input concepts in use
Notes App Part 1: Project Setup & Core UI TextEditingController for title + body, reading values on save, dispose()
Notes App Part 2: Local Persistence & Polish Controllers with async state, prefilling editor from existing note data
Flutter Widgets: Stateless vs Stateful StatefulWidget and setState() — required for all form examples
Show 1 Comment

1 Comment

Leave a Reply