Back to Posts

How to Style TextField in Flutter: Complete Guide

10 min read
<div style="text-align: center;"> <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDMwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPCEtLSBUZXh0RmllbGQgU3R5bGluZyBleGFtcGxlIC0tPgogIDxyZWN0IHdpZHRoPSIzMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRkZGIiBzdHJva2U9IiMwMDAiLz4KICA8dGV4dCB4PSIxNTAiIHk9IjEwMCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMjEyMTIxIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UZXh0RmllbGQgU3R5bGluZzwvdGV4dD4KPC9zdmc+" alt="TextField Styling Example" width="300" /> </div>

TextFields are essential components in any Flutter application. This guide will show you how to create beautifully styled text fields using various techniques and best practices.

Basic TextField Styling

1. Simple Styled TextField

TextField(
  decoration: InputDecoration(
    labelText: 'Username',
    hintText: 'Enter your username',
    prefixIcon: Icon(Icons.person),
    border: OutlineInputBorder(),
    filled: true,
    fillColor: Colors.grey[100],
  ),
)

2. Custom Border Styling

TextField(
  decoration: InputDecoration(
    labelText: 'Email',
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(10),
      borderSide: BorderSide(
        color: Colors.blue,
        width: 2,
      ),
    ),
    enabledBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(10),
      borderSide: BorderSide(
        color: Colors.grey,
        width: 1,
      ),
    ),
    focusedBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(10),
      borderSide: BorderSide(
        color: Colors.blue,
        width: 2,
      ),
    ),
  ),
)

Advanced Styling Techniques

1. Custom Input Decoration

class CustomInputDecoration extends InputDecoration {
  const CustomInputDecoration({
    required String labelText,
    String? hintText,
    IconData? prefixIcon,
  }) : super(
          labelText: labelText,
          hintText: hintText,
          prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
          ),
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: BorderSide(color: Colors.grey[300]!),
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: BorderSide(color: Colors.blue, width: 2),
          ),
          filled: true,
          fillColor: Colors.grey[50],
          contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        );
}

// Usage
TextField(
  decoration: CustomInputDecoration(
    labelText: 'Password',
    hintText: 'Enter your password',
    prefixIcon: Icons.lock,
  ),
)

2. Animated TextField

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

  @override
  State<AnimatedTextField> createState() => _AnimatedTextFieldState();
}

class _AnimatedTextFieldState extends State<AnimatedTextField> {
  bool _isFocused = false;

  @override
  Widget build(BuildContext context) {
    return Focus(
      onFocusChange: (hasFocus) {
        setState(() {
          _isFocused = hasFocus;
        });
      },
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          boxShadow: _isFocused
              ? [
                  BoxShadow(
                    color: Colors.blue.withOpacity(0.2),
                    blurRadius: 8,
                    offset: Offset(0, 2),
                  )
                ]
              : null,
        ),
        child: TextField(
          decoration: InputDecoration(
            labelText: 'Search',
            prefixIcon: Icon(Icons.search),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
            ),
          ),
        ),
      ),
    );
  }
}

Theme-Based Styling

1. Global TextField Theme

MaterialApp(
  theme: ThemeData(
    inputDecorationTheme: InputDecorationTheme(
      filled: true,
      fillColor: Colors.grey[100],
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: BorderSide(color: Colors.grey),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: BorderSide(color: Colors.grey),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: BorderSide(color: Colors.blue, width: 2),
      ),
      contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
    ),
  ),
  home: MyHomePage(),
)

2. Custom Theme Extension

class CustomTextFieldTheme extends ThemeExtension<CustomTextFieldTheme> {
  final Color borderColor;
  final Color focusedBorderColor;
  final double borderRadius;
  final EdgeInsets contentPadding;

  const CustomTextFieldTheme({
    required this.borderColor,
    required this.focusedBorderColor,
    required this.borderRadius,
    required this.contentPadding,
  });

  @override
  ThemeExtension<CustomTextFieldTheme> copyWith({
    Color? borderColor,
    Color? focusedBorderColor,
    double? borderRadius,
    EdgeInsets? contentPadding,
  }) {
    return CustomTextFieldTheme(
      borderColor: borderColor ?? this.borderColor,
      focusedBorderColor: focusedBorderColor ?? this.focusedBorderColor,
      borderRadius: borderRadius ?? this.borderRadius,
      contentPadding: contentPadding ?? this.contentPadding,
    );
  }

  @override
  ThemeExtension<CustomTextFieldTheme> lerp(
    ThemeExtension<CustomTextFieldTheme>? other,
    double t,
  ) {
    if (other is! CustomTextFieldTheme) return this;
    return CustomTextFieldTheme(
      borderColor: Color.lerp(borderColor, other.borderColor, t)!,
      focusedBorderColor:
          Color.lerp(focusedBorderColor, other.focusedBorderColor, t)!,
      borderRadius: lerpDouble(borderRadius, other.borderRadius, t)!,
      contentPadding: EdgeInsets.lerp(contentPadding, other.contentPadding, t)!,
    );
  }
}

// Usage
MaterialApp(
  theme: ThemeData(
    extensions: [
      CustomTextFieldTheme(
        borderColor: Colors.grey,
        focusedBorderColor: Colors.blue,
        borderRadius: 12,
        contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
    ],
  ),
)

Specialized TextField Styles

1. Search Field

class SearchField extends StatelessWidget {
  const SearchField({super.key});

  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: InputDecoration(
        hintText: 'Search...',
        prefixIcon: Icon(Icons.search),
        suffixIcon: IconButton(
          icon: Icon(Icons.clear),
          onPressed: () {
            // Clear text
          },
        ),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(25),
        ),
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
      ),
    );
  }
}

2. Password Field

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

  @override
  State<PasswordField> createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> {
  bool _obscureText = true;

  @override
  Widget build(BuildContext context) {
    return TextField(
      obscureText: _obscureText,
      decoration: InputDecoration(
        labelText: 'Password',
        prefixIcon: Icon(Icons.lock),
        suffixIcon: IconButton(
          icon: Icon(_obscureText ? Icons.visibility : Icons.visibility_off),
          onPressed: () {
            setState(() {
              _obscureText = !_obscureText;
            });
          },
        ),
        border: OutlineInputBorder(),
      ),
    );
  }
}

Best Practices

  1. Consistency

    • Use consistent styling across your app
    • Follow Material Design guidelines
    • Create reusable styles
  2. Accessibility

    • Provide clear labels
    • Use appropriate contrast
    • Include error messages
  3. Performance

    • Avoid unnecessary rebuilds
    • Use const constructors
    • Optimize animations
  4. User Experience

    • Clear visual feedback
    • Proper error handling
    • Intuitive interactions

Common Issues and Solutions

  1. Overlapping Text

    TextField(
      decoration: InputDecoration(
        contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
    )
  2. Custom Error Styling

    TextField(
      decoration: InputDecoration(
        errorStyle: TextStyle(color: Colors.red),
        errorBorder: OutlineInputBorder(
          borderSide: BorderSide(color: Colors.red),
        ),
      ),
    )
  3. Placeholder Styling

    TextField(
      decoration: InputDecoration(
        hintStyle: TextStyle(color: Colors.grey),
        hintText: 'Enter text...',
      ),
    )

Conclusion

Styling TextFields in Flutter offers endless possibilities for creating beautiful and functional user interfaces. Remember to:

  • Follow Material Design guidelines
  • Ensure accessibility
  • Maintain consistency
  • Optimize performance
  • Test thoroughly

Happy styling!