Forms, Validation & User Input

Keyboard Types & Text Input Actions

15 min Lesson 5 of 12

Keyboard Types & Text Input Actions

When you place a TextField in a Flutter form, two properties dramatically improve the user experience: keyboardType and textInputAction. The first controls which keyboard layout the operating system shows, and the second controls what the action button in the bottom-right corner of the keyboard does. Together they eliminate friction by surfacing the right keys at the right moment.

Understanding keyboardType

The keyboardType parameter accepts a TextInputType constant. Flutter passes this hint to the platform (Android/iOS), which then renders the most appropriate keyboard variant. The most commonly used values are:

  • TextInputType.text — default QWERTY keyboard for general text
  • TextInputType.emailAddress — QWERTY with @ and . prominently visible
  • TextInputType.number — numeric keypad (0–9); no decimal or sign by default
  • TextInputType.phone — phone dial pad including +, *, #
  • TextInputType.multiline — regular keyboard with a newline key instead of a submit/done key
  • TextInputType.url — adds / and .com shortcuts on iOS
  • TextInputType.visiblePassword — text keyboard with the suggestion bar hidden
  • TextInputType.numberWithOptions(decimal: true, signed: true) — numeric pad that also allows decimals and negative values
Note: keyboardType is a hint, not a hard constraint. The platform may ignore it in some system configurations. Always add inputFormatters alongside keyboardType if you need strict validation (e.g., prevent non-numeric characters).

Understanding textInputAction

The textInputAction parameter accepts a TextInputAction constant. It changes the label or icon on the keyboard's primary action button:

  • TextInputAction.next — shows a forward arrow or "Next"; typically moves focus to the next field
  • TextInputAction.done — shows "Done" or a checkmark; dismisses the keyboard
  • TextInputAction.search — shows a magnifying-glass icon; suitable for search fields
  • TextInputAction.go — shows "Go"; used for URL or navigation fields
  • TextInputAction.send — shows "Send"; ideal for message composition fields
  • TextInputAction.newline — inserts a newline; used with multiline fields
Tip: Pair textInputAction: TextInputAction.next with the onFieldSubmitted or onSubmitted callback and call FocusScope.of(context).nextFocus() to create a smooth tab-stop flow across multiple form fields.

Code Example 1 — Registration Form with Multiple Keyboard Types

The following form demonstrates four different keyboard types for a user registration screen:

class RegistrationForm extends StatefulWidget {
  const RegistrationForm({super.key});

  @override
  State<RegistrationForm> createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameFocus    = FocusNode();
  final _emailFocus   = FocusNode();
  final _phoneFocus   = FocusNode();
  final _ageFocus     = FocusNode();

  @override
  void dispose() {
    _nameFocus.dispose();
    _emailFocus.dispose();
    _phoneFocus.dispose();
    _ageFocus.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          // Default text keyboard; action moves to email field
          TextFormField(
            focusNode: _nameFocus,
            keyboardType: TextInputType.text,
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(labelText: 'Full Name'),
            onFieldSubmitted: (_) =>
                FocusScope.of(context).requestFocus(_emailFocus),
          ),

          // Email keyboard (@, . are easy to reach); action moves to phone
          TextFormField(
            focusNode: _emailFocus,
            keyboardType: TextInputType.emailAddress,
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(labelText: 'Email'),
            onFieldSubmitted: (_) =>
                FocusScope.of(context).requestFocus(_phoneFocus),
          ),

          // Phone dial-pad (+, *, #); action moves to age field
          TextFormField(
            focusNode: _phoneFocus,
            keyboardType: TextInputType.phone,
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(labelText: 'Phone Number'),
            onFieldSubmitted: (_) =>
                FocusScope.of(context).requestFocus(_ageFocus),
          ),

          // Pure numeric pad; Done closes the keyboard
          TextFormField(
            focusNode: _ageFocus,
            keyboardType: TextInputType.number,
            textInputAction: TextInputAction.done,
            decoration: const InputDecoration(labelText: 'Age'),
            onFieldSubmitted: (_) => _submitForm(),
          ),

          ElevatedButton(
            onPressed: _submitForm,
            child: const Text('Register'),
          ),
        ],
      ),
    );
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      // process registration…
    }
  }
}

Code Example 2 — Search Field & Multiline Notes

Below are two specialized field patterns you will use repeatedly in real apps:

// --- Search field ---
// textInputAction.search shows a magnifying-glass icon on the action button.
// Wrap the callback to trigger your search logic immediately.
TextField(
  keyboardType: TextInputType.text,
  textInputAction: TextInputAction.search,
  decoration: const InputDecoration(
    prefixIcon: Icon(Icons.search),
    hintText: 'Search products…',
    border: OutlineInputBorder(),
  ),
  onSubmitted: (query) => _runSearch(query),
),

// --- Multiline notes field ---
// keyboardType.multiline MUST be paired with maxLines != 1 so the field
// expands. textInputAction.newline keeps the Enter key as a real newline
// instead of dismissing the keyboard.
TextField(
  keyboardType: TextInputType.multiline,
  textInputAction: TextInputAction.newline,
  maxLines: 5,
  decoration: const InputDecoration(
    labelText: 'Notes',
    alignLabelWithHint: true,
    border: OutlineInputBorder(),
  ),
),

// --- Decimal price field ---
// numberWithOptions(decimal: true) shows the decimal separator (.  or ,)
// on the numeric pad, which plain TextInputType.number omits on some devices.
TextField(
  keyboardType: const TextInputType.numberWithOptions(decimal: true),
  textInputAction: TextInputAction.done,
  decoration: const InputDecoration(
    labelText: 'Price',
    prefixText: '\$ ',
  ),
),
Warning: Never use keyboardType: TextInputType.multiline without also setting maxLines to a value other than 1 (or null for unlimited). If maxLines remains at its default of 1, the field will not grow and the newline key will have no visible effect, confusing users.

Decimal & Signed Numbers

Use TextInputType.numberWithOptions(decimal: true, signed: true) when your field must accept prices, measurements, or temperatures. The decimal flag adds a period/comma key, and the signed flag adds a minus key — both absent from the plain numeric keyboard on many devices.

Platform Differences to Keep in Mind

  • On iOS, TextInputType.number shows a simple number pad; the decimal separator key only appears when decimal: true is set.
  • On Android, the numeric keyboard usually includes a decimal key anyway, but behaviour varies by OEM keyboard.
  • The Done / Next label text may be localised by the OS; rely on the icon or position, not the string label, in your UI copy.

Summary

Setting keyboardType and textInputAction is one of the quickest wins in form UX. Use the email keyboard for email fields, the numeric pad for ages and prices, the phone pad for phone numbers, and multiline for free-text notes. Pair TextInputAction.next with focus-node chaining so users can tab through your entire form without touching the screen, and use TextInputAction.done or TextInputAction.search as the final action to submit or dismiss.