Register Screen
Full name, email, password registration with confirm password validation
View component →A complete registration screen featuring an interactive role selector grid — Owner, Manager, or Employee. Built for team-based SaaS apps where user roles determine permissions from day one. Includes full form validation, password visibility toggle, and loading state.
Create Account
Start managing your team's tasks and performance in minutes.
Full Name
Email Address
Password
I am a...
Already have an account? Log in
WHAT'S INCLUDED
USE CASES
PRO TIP
Store the selected role in your auth provider's user metadata at sign-up time rather than in a separate database call. With Firebase Auth, pass it as displayName or use custom claims via Cloud Functions. This way the role is available immediately after login without an extra Firestore read.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
class SignUpWithRoleSelector extends StatefulWidget {
const SignUpWithRoleSelector({super.key});
@override
State<SignUpWithRoleSelector> createState() => _SignUpWithRoleSelectorState();
}
class _SignUpWithRoleSelectorState extends State<SignUpWithRoleSelector> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
bool _obscurePassword = true;
String _selectedRole = 'Owner';
final List<Map<String, dynamic>> _roles = [
{'label': 'Owner', 'icon': Icons.workspace_premium_outlined},
{'label': 'Manager', 'icon': Icons.work_outline},
{'label': 'Employee', 'icon': Icons.person_outline},
];
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _handleSignUp() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
// TODO: Replace with your Firebase/Supabase/API sign up logic
// Pass _selectedRole to your auth provider as user metadata
await Future.delayed(const Duration(seconds: 2));
if (mounted) setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF111827)),
onPressed: () => Navigator.pop(context),
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Create Account',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFF111827),
),
),
const SizedBox(height: 8),
const Text(
"Start managing your team's tasks and performance in minutes.",
style: TextStyle(
fontSize: 15,
color: Color(0xFF6B7280),
height: 1.5,
),
),
const SizedBox(height: 28),
// Full Name
const Text(
'Full Name',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF111827)),
),
const SizedBox(height: 8),
TextFormField(
controller: _nameController,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Jane Doe',
hintStyle: const TextStyle(color: Color(0xFF6B7280)),
filled: true,
fillColor: const Color(0xFFF9FAFB),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF4F46E5)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
validator: (value) {
if (value == null || value.trim().isEmpty) return 'Please enter your name';
if (value.trim().length < 3) return 'Name must be at least 3 characters';
return null;
},
),
const SizedBox(height: 20),
// Email
const Text(
'Email Address',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF111827)),
),
const SizedBox(height: 8),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'jane@example.com',
hintStyle: const TextStyle(color: Color(0xFF6B7280)),
filled: true,
fillColor: const Color(0xFFF9FAFB),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF4F46E5)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
validator: (value) {
if (value == null || value.isEmpty) return 'Please enter your email';
final regex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!regex.hasMatch(value)) return 'Please enter a valid email';
return null;
},
),
const SizedBox(height: 20),
// Password
const Text(
'Password',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF111827)),
),
const SizedBox(height: 8),
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _handleSignUp(),
decoration: InputDecoration(
hintText: '••••••••',
hintStyle: const TextStyle(color: Color(0xFF6B7280)),
filled: true,
fillColor: const Color(0xFFF9FAFB),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF4F46E5)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined,
color: const Color(0xFF6B7280),
),
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
),
),
validator: (value) {
if (value == null || value.isEmpty) return 'Please enter a password';
if (value.length < 8) return 'Minimum 8 characters';
if (!value.contains(RegExp(r'[0-9]'))) return 'Must contain at least one number';
return null;
},
),
const SizedBox(height: 24),
// Role Selector
const Text(
'I am a...',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF111827)),
),
const SizedBox(height: 10),
Row(
children: _roles.map((role) {
final isActive = _selectedRole == role['label'];
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedRole = role['label'] as String),
child: Container(
margin: EdgeInsets.only(
right: role['label'] != 'Employee' ? 8 : 0,
),
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isActive ? const Color(0xFFEEF2FF) : Colors.white,
border: Border.all(
color: isActive ? const Color(0xFF4F46E5) : const Color(0xFFE5E7EB),
),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
role['icon'] as IconData,
size: 22,
color: isActive ? const Color(0xFF4F46E5) : const Color(0xFF6B7280),
),
const SizedBox(height: 6),
Text(
role['label'] as String,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isActive ? const Color(0xFF4F46E5) : const Color(0xFF111827),
),
),
],
),
),
),
);
}).toList(),
),
const SizedBox(height: 32),
// Sign Up Button
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleSignUp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4F46E5),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(9999),
),
elevation: 0,
),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
)
: const Text(
'Sign Up',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
),
const SizedBox(height: 20),
// Login Link
Center(
child: RichText(
text: TextSpan(
style: const TextStyle(fontSize: 14, color: Color(0xFF6B7280)),
children: [
const TextSpan(text: 'Already have an account? '),
WidgetSpan(
child: GestureDetector(
onTap: () {
// Navigator.pushReplacementNamed(context, '/login');
},
child: const Text(
'Log in',
style: TextStyle(
fontSize: 14,
color: Color(0xFF4F46E5),
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
const SizedBox(height: 24),
],
),
),
),
),
);
}
}
Full name, email, password registration with confirm password validation
View component →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 →