Register Screen
Full name, email, password registration with confirm password validation
View component →A complete, production-ready login screen built with Flutter's Form widget. Handles email validation, password visibility toggle, loading state during API calls, and clean error display. Works with any authentication provider — Firebase, Supabase, or a custom REST API.
Welcome back
Forgot password?
Don't have an account? Register
WHAT'S INCLUDED
USE CASES
PRO TIP
Wrap your entire screen in a SingleChildScrollView so the layout does not break when the keyboard appears on smaller devices. Also use FocusNode to auto-advance focus from the email field to the password field when the user taps next on the keyboard.
Copy this entire snippet into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) return 'Email is required';
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) return 'Enter a valid email';
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) return 'Password is required';
if (value.length < 6) return 'Password must be at least 6 characters';
return null;
}
Future<void> _onLogin() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 48),
Text(
'Welcome back',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
'Sign in to continue',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
),
const SizedBox(height: 32),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: _validateEmail,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.mail_outline),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
validator: _validatePassword,
onFieldSubmitted: (_) => _onLogin(),
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
onPressed: () =>
setState(() => _obscurePassword = !_obscurePassword),
),
border: const OutlineInputBorder(),
),
),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {},
child: const Text('Forgot password?'),
),
),
const SizedBox(height: 8),
FilledButton(
onPressed: _isLoading ? null : _onLogin,
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(52),
),
child: _isLoading
? const SizedBox(
height: 22,
width: 22,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Colors.white,
),
)
: const Text('Login',
style: TextStyle(fontSize: 16)),
),
],
),
),
),
),
);
}
}
Full name, email, password registration with confirm password validation
View component →Email input with send reset link flow and success state
View component →6-digit PIN input with auto-focus, auto-advance, and shake on error
View component →