Login Screen
Clean email + password login with form validation and loading state
View component →A full registration screen with four validated fields — full name, email, password, and confirm password. Includes password strength checking, confirm password match validation, and a terms & conditions checkbox. Built to plug directly into any Flutter auth flow without modification.
Create Account
Join us today
Already have an account? Login
WHAT'S INCLUDED
USE CASES
PRO TIP
Use TextEditingController for both the password and confirm password fields and compare their .text values inside the confirmPassword validator function. Never compare inside onChanged — validators run at the right time and give you proper inline error messages automatically without extra setState calls.
Copy this entire snippet into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _isLoading = false;
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
bool _agreedToTerms = false;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
Future<void> _handleRegister() async {
if (!_formKey.currentState!.validate()) return;
if (!_agreedToTerms) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please agree to Terms & Conditions')),
);
return;
}
setState(() => _isLoading = true);
// TODO: Replace with your auth logic
await Future.delayed(const Duration(seconds: 2));
setState(() => _isLoading = false);
// Navigate to home or login
// Navigator.pushReplacementNamed(context, '/home');
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Create Account',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
'Join us today',
style: TextStyle(
fontSize: 15,
color: Colors.grey[500],
),
),
const SizedBox(height: 32),
// Full Name
TextFormField(
controller: _nameController,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter your full name';
}
if (value.trim().length < 3) {
return 'Name must be at least 3 characters';
}
return null;
},
),
const SizedBox(height: 16),
// Email
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: 'Email Address',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
// Password
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: 'Password',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
),
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'Password must contain at least one number';
}
return null;
},
),
const SizedBox(height: 16),
// Confirm Password
TextFormField(
controller: _confirmPasswordController,
obscureText: _obscureConfirmPassword,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _handleRegister(),
decoration: InputDecoration(
labelText: 'Confirm Password',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword ? Icons.visibility_off : Icons.visibility,
),
onPressed: () => setState(
() => _obscureConfirmPassword = !_obscureConfirmPassword,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please confirm your password';
}
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
},
),
const SizedBox(height: 20),
// Terms & Conditions
Row(
children: [
Checkbox(
value: _agreedToTerms,
activeColor: const Color(0xFF1D9E75),
onChanged: (value) => setState(() => _agreedToTerms = value ?? false),
),
Expanded(
child: Text(
'I agree to the Terms & Conditions',
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
),
),
],
),
const SizedBox(height: 24),
// Register Button
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleRegister,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1D9E75),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text('Create Account', style: TextStyle(fontSize: 16)),
),
),
const SizedBox(height: 20),
// Login Link
Center(
child: TextButton(
onPressed: () {
// Navigator.pushReplacementNamed(context, '/login');
},
child: Text(
'Already have an account? Login',
style: TextStyle(color: Colors.grey[600], fontSize: 13),
),
),
),
],
),
),
),
),
);
}
}
Clean email + password login with form validation and loading state
View component →6-digit PIN input with auto-focus, auto-advance, and shake on error
View component →Animated splash with logo fade-in and auto-navigate after delay
View component →