Flutter TextFormField – Login Screen for a Flutter Mobile App

Flutter TextFormField - Login screen

Build a user-friendly Login screen in Flutter with TextFormField. This step by step guide covers the creation of a simple yet effective login screen using the flutter TextFormField widget. Enhance the user experience of your app with a seamless Flutter Login screen. Learn how to create an efficient sign-in page that provides customers access to their account information.

Code Structure and Demo

First, let’s take a look at the main components used in the app and at how the end result will look like. The main elements in the Login Screen are:

  • Form – Used for wrapping the FormFields so we can validate the fields on Login button tap.
  • Username TextFormField – The input field used to enter the username.
  • Password TextFormField – The input field used to enter the password.
  • Login Button – The button that will trigger the Form validation and the SignIn business logic.

Form widget for validation

We will start by adding a Form widget. The main purpose of the Form widget is to group fields together. Grouping a list of form fields allows you to control the state of the text fields. You can validate, save or reset them.

In order to have access to the Form anywhere in our widget, we will start by defining a formKey:

   final _formKey = GlobalKey<FormState>();

Once we defined our formKey we can go ahead and create our Form and reference the key:

  Form(key: _formKey,
     child: Column(..))

Flutter TextFormField

The most important widget in a SignIn screen is the flutter TextFormField. This widget is a wrapper around the FormField which is responsible with storing the state of form field and handling the validation. The TextFormField is adding a TextField inside a FormField.

Create stateless widget

Most likely you will have to customize the flutter TextFormField widget to make it look nice. Given all the customizations that we have to make, it is better to create a stateless widget where we make the customizations. This way, we can style the widget once and we can use it across the app whenever we need an input box.

Why a stateless widget? It could save us memory and improve the performance of the app. You can read more about it here.

We will call our helper widget JDInputText. Why do we use a class prefix (JD in this case)? In general, it is a good idea to prefix your classes to avoid clashes with widgets from other packages.

 class JDInputText extends StatelessWidget {
  const JDInputText({
    required this.onChanged,
    required this.hintText,
    this.obscureText = false,
  final Function(String?) onChanged;
  final String? Function(String?)? validator;
  final String hintText;
  final bool obscureText;
  final TextInputType? keyboardType;
  final Widget? suffixIcon;
  final TextInputAction? textInputAction;

Pass widget properties to flutter TextFormField

So far, we defined a stateless widget with all the required properties to build our TextFormField. On top of the properties mentioned above, our text field will contain styling properties for for text, border, background color etc.

  Widget build(BuildContext context) {
    return TextFormField(
      obscureText: obscureText,
      validator: validator,
      onChanged: onChanged,
      keyboardType: keyboardType ?? TextInputType.text,
      textInputAction: textInputAction,

The properties of the flutter TextFormField should be self explanatory:

  • obscureText controls if we should hide the text in the input (this is used for hiding the password)
  • validator function that when run it should return null if the text is valid. If the text is not valid, it should return a string indicating why the input is not correct.
  • onChanged function that will be called every time the input changes. Every time a user types something and the input box changes this function will be called.
  • keyboardType used for indicating what type of keyboard we want to display. In our case, we will use a special keyboard for email that adds on the screen an @ character.
  • textInputAction used to indicate what is the action button on the keyboard. In our case, the username/email field will have a Next action, while the password field will have a Done button.

Styling the flutter TextFormField

Now that we passed the widget properties for handling the rendering logic we should add some styling to our input field.

      decoration: InputDecoration(
        suffixIcon: suffixIcon,
        contentPadding: const EdgeInsets.fromLTRB(16, 0, 0, 0),
        hintText: hintText,
        fillColor: Theme.of(context).backgroundColor,
        filled: true,
        errorStyle: TextStyle(height: 0, color: Colors.transparent),
        hintStyle: TextStyle(
          fontSize: 16,
          color: Color(0xFF969A9D),
          fontWeight: FontWeight.w300,
        border: OutlineInputBorder(
            borderRadius: BorderRadius.zero,
            borderSide: BorderSide(color: Color(0xFF707070), width: .5)),
        enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.zero,
            borderSide: BorderSide(color: Color(0xFF707070), width: .5)),
        focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.zero,
            borderSide: BorderSide(color: Color(0xFF707070), width: .5)),
        errorBorder: OutlineInputBorder(
          borderRadius: BorderRadius.zero,
          borderSide: BorderSide(color: Colors.red, width: .5),
      style: TextStyle(
        fontSize: 16,
        color: Color(0xFF3C3C43),

In order to make our input text field look nice we have to do a few style changes to it:

  • decoration – the main place where you can change the style of the input.
  • suffixIcon – used for passing in the password icon.
  • hintText – passing a hint text for the input text field.
  • hintStyle – changing the style of the hint text.
  • border – we can specify different borders types for different states.

Using a stateless widget to create the email and password fields

Now that we defined a custom stateless widget that looks nice, we can reuse it to create our two inputs, one for email and one for password.

Email text field

/// Email Address input
    hintText: 'Enter Email Address',
    keyboardType: TextInputType.emailAddress,
    textInputAction: TextInputAction.next,
    onChanged: (val) {
      _email = val;
    validator: (email) {
      if (email != null && EmailValidator.validate(email)) {
        return null;
      return "Invalid email address";

As you can see above it is really easy to reuse the stateless widget that we crated, the code is more readable because we focus on the business logic instead of styling the widget.

You may have noticed that we used an EmailValidator class. This is from a custom package that does really good job at doing client side email validation. You can install it by adding the following line in pubspec.yaml.

   email_validator: ^2.0.1

Password text field

A common feature that password fields have is the ability to show the obscured password. This could help users verify the password if they are having problems signing in your app.

In order to create the functionality of toggling between a visible password field and an obscured one, we need to keep a local state in our widget.

   bool _visiblePassword = false;

We called it visiblePassword and it will be true when we want to have the password field visible. We also use a different suffix icon for the field when the state is true, so we use this state to control what icon we show.

 /// Password input
    obscureText: !_visiblePassword,
    hintText: 'Enter Password',
    textInputAction: TextInputAction.done,
    keyboardType: TextInputType.visiblePassword,
    onChanged: (val) {
      _password = val;
    validator: (password) {
      if (password == null || password.length == 0) {
        return "Empty password";
      return null;
    suffixIcon: InkWell(
      onTap: () {
        setState(() {
          _visiblePassword = !_visiblePassword;
      child: Icon(
            ? Icons.visibility_off
            : Icons.visibility,
        color: Theme.of(context).primaryColor,

Login Button

Once we have our input fields on the screen, all we need to do is to add a Login button and to use the _formKey that we defined initially to validate our flutter TextFormField widgets.

    style: ButtonStyle(
      textStyle: MaterialStateProperty.all(
        TextStyle(color: Colors.white),
    onPressed: () async {
      if (!_formKey.currentState!.validate()) {
        setState(() {
          _error =
              'Please provide a valid email/password combination';
      } else {
        /// Add business logic to authenticate user.
        /// User _email and _password
        setState(() {
          _error = '';
    child: Text(
      'Log In',
      style: TextStyle(color: Colors.white, fontSize: 16),

Once the user taps on the button we call validate() on the state of our form. If the form is not valid, we will show an error by using an error string in the widget.

That’s it. We are almost ready. We just need to add the a text that renders the _error string and we are good to go.

   String? _error;


I hope you like this article and it was useful to you. A flutter TextFormField widget is quite powerful and it allows us to have nice custom Sign In or Create account forms.

You can download the full code here.

Thank you! Happy flutter coding! Please let us know in the comments section if this was useful to you.

One thought on “Flutter TextFormField – Login Screen for a Flutter Mobile App

Leave a Reply

Your email address will not be published. Required fields are marked *