Ishan Fernando Blogger | Flutter | Android | iOS

The ultimate guide to text fields in Flutter

5 min read 1587

Most modern applications require some type of input from a user. Whether it be a signup, login, or feedback form, learning how to implement a text field is an important skill to master as a developer.

In Flutter, there are two types of text field widgets that we can use to get user input. One is TextField and the other one is TextFormField, a slightly more advanced version of TextField. TextFormField provides more functionalities than TextField, such as build form validation and the ability to set initial text value directly.

If your text field requires only one or two inputs from the user, I suggest using the TextField widget. Otherwise if you want to create a larger form with multiple input fields and validation, it’s better to go with the TextFormField widget.

Creating a basic TextField

Creating a basic TextField widget is straightforward. Apply the TextField widget inside your widget tree where you want it to appear. This will add a default TextField with default styling:

TextField(
    )

Creating a basic TextFormField

You can add TextFormField in the same manner as TextField. There is no visual difference between these two widgets:

TextFormField(
    )

Styling a text field

Styling a text field to personalize your application is easily done by setting InputDecoration to the decoration property of the TextField/TextFormField widget:

     TextField(
            decoration: InputDecoration(
              filled: true,
              fillColor: Colors.blueAccent,
              border: OutlineInputBorder(
                borderSide: BorderSide.none,
                borderRadius: BorderRadius.circular(50)
              ),
            ),
          )

You must set the filled value to true if you want to apply a background color to your text field. Otherwise, the background color will not be affected.

screenshot of blue text field

Changing text color

Text color can be changed using the style property of the TextField widget. You can also change the cursor color by setting the color to the cursorColor property:

        TextField(
              cursorColor: Colors.black,
              style: TextStyle(
                color: Colors.white
              ),
              decoration: InputDecoration(
                filled: true,
                fillColor: Colors.blueAccent,
                border: OutlineInputBorder(
                  borderSide: BorderSide.none,
                  borderRadius: BorderRadius.circular(50)
                ),
              ),
            )

screenshot of white text in a blue form field

Adding hint text

Hint text is used to give users an idea about the input values that are accepted by the text field. You can use the hintText property to add a hint to the text field which will disappear when you begin typing. The default color is grey, but you can add hintStyle to change the text styling:

    TextField(
        decoration: InputDecoration(
          hintStyle: TextStyle(color: Colors.blue),
          hintText: "Enter your name"
        ),
      )

Screenshot of a text field with "enter your name" displayed as hint text

Adding multi-line support

By default, TextField shows as a single line. But we can specify the maximum number of lines to be supported via the maxLines property. This will not limit the number of lines you can add, it only shows the specified number of lines at a time. If you want to expand the field based on the amount of input text, you can set null to the maxLines property:

        TextField(
          maxLines: 5,
        ),

Screenshot of text field set to 5 lines

Reading input value

Reading the user’s input is the most important feature of your text field. In Flutter, this can be done using TextEditingController.

First, create a TextEditingController and set it as a controller property of your TextField widget.

In this example, I have added an extra Button and Text widget which will show the added text when you click the “Show Text” button.

When you press the button, it will set the textController value to the displayText variable. displayText has been set as the text of the Text widget, so when you press “Show Text” you can see the input text appear:

class _TextFormState extends State<TextFormSample> {
  TextEditingController textController = TextEditingController();
  String displayText = "";

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: textController,
          maxLines: null,
        ),
        ElevatedButton(onPressed: (){
          setState(() {
            displayText = textController.text;
          });
        }, child: Text("Show Text")),
        Text(displayText,style: TextStyle(fontSize: 20),)
      ],
    );
  }
}

Screenshot of text field text with button that says "show text"

Pre-populating text

Pre-populating values when loading the text field will be useful in scenarios like profile updates and login screens. The TextField widget itself doesn’t have a property to set an initial value, but this can be done using TextEditingController.

Create a TextEditingController, set a value to the text property of the constructor, and it will populate to the widget when it loads the first time:

  TextEditingController textController = TextEditingController(text: "Initial Text");

  @override
    Widget build(BuildContext context) {
      return Center(
        child: TextField(
          controller: textController,
        ),
      );
    }

Then use the initialValue property in TextFormField to create your pre-populated text:

        TextFormField(
          initialValue: "Initial Text",
        )

Changing keyboards based on input type

You may have seen applications show different keyboard layouts for different input types, like number pads for phone numbers or an “@” button for emails. This can be done in Flutter via the keyboardType property. It accepts TextInputType with multiple options like number, date, phone, name, and email address:

     TextField(
        keyboardType: TextInputType.number,
      )

Screenshot of a keyboard showing a number pad

Screenshot of a keyboard showing custom email buttons

Converting a normal text field to a password field

By setting the obscureText property to true you can convert a plain text field to a password field, which masks the input values.

The default of this property will show dots to mask password characters. But you can change this by setting the obscuringCharacter value to anything you’d like; here, I chose asterisks:

     TextField(
        obscureText: true,
        obscuringCharacter: "*",
      )

Screenshot showing a text field entry obscured by asterisks

Restricting the number of characters

The maxLength property accepts integer values to specify the maximum number of characters accepted by the particular field. After adding this property, if users enter a value with more characters than specified in maxLength, it will block the input automatically:

     TextField(
        maxLength: 2,
      )

Screenshot showing the number 23 in a text field with a character counter in the bottom right

Restricting and allowing input values

Utilizing validation in your text field to restrict certain characters or digits is a must to reduce user errors.



Flutter’s inputFormatter property allows you to set an array of filters to the TextField widget. It will accept two types:

  1. Allowing specific characters, which can be set using FilteringTextInputFormatter.allow()
  2. Denying specific characters, which can be set using FilteringTextInputFormatter.deny()

The following is an example of what your code might look like if you’re denying certain characters:

TextField(
        inputFormatters: [FilteringTextInputFormatter.deny(RegExp("[0-9]+"))],
      )

If a user enters a denied character, the text field will not display an error to the user. It simply blocks or allows specified characters based on the input.

However, adding error messages with validation is simple, which is what we are going to talk about next.

Validating input with error messages

Applying an error message to TextField and TextFormField is slightly different because of the availability of certain properties. Let’s take a look at how you can validate input with error messages in each of these widgets.

Input validation error messages in TextField

There is no direct property to add an error message in TextField. But you can set an errorText property in InputDecoration based on the validated value.

In the following example, I determine if the input value is empty and a number, and change the isANumber value to true or false based on the result. Based on the isANumber value you can set the error text, as I did here with “Please enter a number”:

class _LoginFormState extends State<LoginForm> {

  TextEditingController textController = TextEditingController();
  RegExp digitValidator  = RegExp("[0-9]+");
  bool isANumber = true;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: 
      TextField(
        onChanged: (inputValue){
          if(inputValue.isEmpty || digitValidator.hasMatch(inputValue)){
            setValidator(true);
          } else{
            setValidator(false);
          }
        },

        decoration: InputDecoration(
          errorText: isANumber ? null : "Please enter a number"
        ),
      ),
    );
  }
  void setValidator(valid){
    setState(() {
      isANumber = valid;
    });
  }
}

Screenshot of text field with input that reads "my name" and red error message that reads "please enter a number"

You can easily customize error text color by setting TextStyle to the errorStyle property.

You can change the border color using the focusedErrorBorder and errorBorder properties. errorBorder will be shown when there is no focus on the field. Therefore, be sure to set both of those properties when changing the border color:

TextField(
        onChanged: (inputValue){
          if(inputValue.isEmpty || digitValidator.hasMatch(inputValue)){
            setValidator(true);
          } else{
            setValidator(false);
          }
        },

        decoration: InputDecoration(
          errorText: isANumber ? null : "Please enter a number",
          errorStyle: TextStyle(color: Colors.purpleAccent),
          focusedErrorBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.purpleAccent)),
          errorBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.purpleAccent))
        ),
      )

Screenshot of text field with error message that says "please enter a number" in purple

Input validation error messages in TextFormField

The main difference between TextFormField and TextField is that the TextFormField widget uses the Form widget, which can contain multiple TextField widgets.

In Flutter, creating a validated Form with TextFormField is simple.


More great articles from LogRocket:


First, create a Form widget and add two TextFormField widgets with a button (I used ElevatedButton) and Text.

The important thing to remember when creating a Form widget is that you must first create a GlobalKey which is required to access the Form. After creating a GlobalKey, you can set that key to the key property of the Form widget.

TextFormField contains a property called a validator. You can access field values in the validator callback function and validate differently based on the returned value. For the first text field, we will check whether it is empty, or whether the value is a digit using a regular expression. If that condition fails you can return an error message for that particular field.

In the onPressed event, you can check the form validity using the GlobalKey object and change the isValidForm value to true or false to show a message in the below Text widget:

class _NumberFormState extends State<NumberForm> {

  var _numberForm = GlobalKey<FormState>();
  RegExp _digitRegex = RegExp("[0-9]+");
  bool isValidForm = false;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Form(
        key: _numberForm,
        child: 
        Column(
          children: [
            TextFormField(
              validator: (inputValue){
                if(inputValue.isEmpty || !_digitRegex.hasMatch(inputValue)){
                  return "Please enter number";
                }
                return null;
              },
            ),
            TextFormField(
              validator: (inputValue){
                if(inputValue.isEmpty){
                  return "Please Fill before";
                }
                return null;
              },
            ),
            ElevatedButton(
              onPressed: (){
                if(_numberForm.currentState.validate()){
                    setState(() {
                      isValidForm = true;
                    });
                } else{
                  setState(() {
                      isValidForm = false;
                    });
                }
            }, child: Text("Check Number")),
            Text( isValidForm ? "Nice" : "Please Fix error and Submit ")
          ],
        )),
    );
  }
}

Screenshot of incomplete text field with an error message that reads "please fix error and submit"

Conclusion

I hope this article gave you a better idea about how to customize and use different properties in Flutter’s TextField and TextFormField widgets.

Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
Ishan Fernando Blogger | Flutter | Android | iOS

Leave a Reply