Flutter OTP Kit
A comprehensive, production-ready Flutter package for OTP (One-Time Password) verification with extensive customization options, responsive design, and robust error handling.
β¨ Features
π― Core Functionality
- Complete OTP Verification: Full-featured OTP input with validation and verification
- Multiple Input Types: Numeric, alphabetic, alphanumeric, and custom input support
- Smart Paste Detection: Automatic clipboard detection and OTP filling
- Real-time Validation: Live validation with custom regex patterns and error messages
- Auto-submit: Automatic form submission when OTP is complete
- Timer Integration: Built-in countdown timer for resend functionality
π¨ Design & Customization
- Comprehensive Styling: Complete control over colors, dimensions, spacing, and appearance
- Advanced Field Shapes: Rectangle, rounded rectangle, circle, stadium, and custom shapes
- Gradient Support: Linear, radial, and sweep gradient backgrounds
- Shadow Effects: Customizable shadows with color, blur, spread, and offset control
- Animation System: Smooth animations with predefined configurations (default, fast, smooth, disabled)
- Enhanced Field Transitions: Smooth field-to-field visual transitions and state animations
- Progressive Highlighting: Visual feedback showing user progress through OTP entry
- Theme Integration: Material Design 3, light, dark themes with automatic adaptation
π± Responsive & Layout
- Responsive Design: Automatic adaptation to different screen sizes and orientations
- Single Row Layout: Standard OTP appearance with fields always in a single row
- Smart Spacing: Dynamic field spacing with min/max constraints and safety margins
- Overflow Protection: Comprehensive overflow prevention with multiple safety layers
- Cross-platform: Optimized for iOS, Android, Web, and Desktop
βΏ Accessibility & UX
- Screen Reader Support: Complete accessibility with semantic labels and hints
- Keyboard Navigation: Full keyboard navigation support
- Focus Management: Proper focus handling and auto-focus capabilities
- Haptic Feedback: Optional haptic feedback for better user experience
- Interactive Selection: Configurable text selection behavior
- Contact Masking: Automatic phone/email masking for privacy
π§ Advanced Features
- Custom Validators: Complete validation control with custom logic
- Input Formatters: Support for custom TextInputFormatter
- Biometric Input: Support for advanced input methods
- Voice Input: Voice input capabilities
- Swipe Navigation: Touch gesture support for field navigation
- Custom Field Builders: Complete control over field appearance and behavior
- Custom Layout Builders: Full control over field arrangement
- Automatic Error State Management: Built-in error state handling with auto-clear functionality
- Widget-Based Customization: Complete UI customization with custom widgets for all components
π Quick Start
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
flutter_otp_kit: ^1.4.0
Then run:
flutter pub get
Basic Usage
import 'package:flutter/material.dart';
import 'package:flutter_otp_kit/flutter_otp_kit.dart';
class MyOTPPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: OtpVerificationWidget(
title: 'Verify Phone Number',
subtitle: 'Enter the 5-digit code sent to {contactInfo}',
contactInfo: '01012345678',
maskingType: MaskingType.phone,
fieldCount: 5,
timerDuration: 60,
buttonText: 'Verify',
resendText: 'Resend Code',
timerPrefix: 'after',
onVerify: (otp) {
// Handle OTP verification
print('OTP: $otp');
},
onResend: () {
// Handle resend functionality
print('Resend requested');
},
),
),
);
}
}
π API Reference
OtpVerificationWidget
The main widget for OTP verification with comprehensive customization options.
Required Parameters
Parameter | Type | Description |
---|---|---|
title |
String |
Main title displayed above OTP fields |
subtitle |
String |
Subtitle with instructions (use {contactInfo} for dynamic content) |
buttonText |
String |
Text for the verification button |
resendText |
String |
Text for the resend button |
timerPrefix |
String |
Text prefix for the countdown timer |
onVerify |
Function(String) |
Callback when OTP verification is requested |
onResend |
Function() |
Callback when resend is requested |
Optional Parameters
Parameter | Type | Default | Description |
---|---|---|---|
contactInfo |
String? |
null |
Contact information to display (phone/email) |
maskingType |
MaskingType |
MaskingType.phone |
Type of contact masking |
fieldCount |
int |
5 |
Number of OTP input fields |
timerDuration |
int |
60 |
Countdown timer duration in seconds |
fieldWidth |
double |
55.125 |
Width of each OTP field |
fieldHeight |
double |
60.731 |
Height of each OTP field |
borderRadius |
double |
17.752 |
Border radius for fields |
borderWidth |
double |
1.869 |
Border width for fields |
spacing |
double |
16.0 |
General spacing around the widget |
fieldSpacing |
double |
12.0 |
Spacing between OTP fields |
minFieldSpacing |
double |
8.0 |
Minimum spacing between fields |
maxFieldSpacing |
double |
20.0 |
Maximum spacing between fields |
primaryColor |
Color |
Color(0xFF018CC3) |
Primary color for the widget |
secondaryColor |
Color |
Color(0xFF8B8B8B) |
Secondary color for borders and text |
backgroundColor |
Color |
Colors.white |
Background color for fields |
autoFocus |
bool |
true |
Auto-focus the first field |
enableAutoValidation |
bool |
true |
Enable automatic validation |
otpInputType |
OtpInputType |
OtpInputType.numeric |
Type of input allowed |
enablePaste |
bool |
true |
Enable clipboard paste detection |
obscureText |
bool |
false |
Hide input text (for sensitive OTPs) |
obscuringCharacter |
String |
'β’' |
Character used for obscuring text |
showTimer |
bool |
true |
Show/hide the countdown timer |
enableShadow |
bool |
false |
Enable shadow effects |
shadowColor |
Color? |
null |
Color of the shadow |
shadowBlurRadius |
double |
10.0 |
Blur radius of the shadow |
shadowSpreadRadius |
double |
0.0 |
Spread radius of the shadow |
focusedBorderColor |
Color? |
null |
Color of focused field border |
errorBorderColor |
Color? |
null |
Color of error field border |
filledFieldBackgroundColor |
Color? |
null |
Background color of filled fields |
cursorColor |
Color? |
null |
Color of the text cursor |
cursorHeight |
double? |
fieldHeight - 12 |
Height of the text cursor (perfect vertical centering) |
cursorWidth |
double |
1.0 |
Width of the text cursor |
cursorAlignment |
CursorAlignment |
CursorAlignment.center |
Alignment of the cursor within the field |
animationDuration |
Duration |
Duration(milliseconds: 150) |
Duration of animations |
animationCurve |
Curve |
Curves.easeInOut |
Animation curve |
textCapitalization |
TextCapitalization |
TextCapitalization.none |
Text capitalization behavior |
enableInteractiveSelection |
bool |
true |
Enable text selection |
layoutType |
OtpLayoutType |
OtpLayoutType.singleRow |
Layout type for field arrangement |
fieldAlignment |
OtpFieldAlignment |
OtpFieldAlignment.center |
Field alignment within layout |
fieldDirection |
OtpFieldDirection |
OtpFieldDirection.horizontal |
Field arrangement direction |
fieldShape |
OtpFieldShape |
OtpFieldShape.roundedRectangle |
Field shape type |
animationConfig |
OtpAnimationConfig |
OtpAnimationConfig.defaultConfig |
Animation configuration |
themeConfig |
OtpThemeConfig? |
null |
Theme configuration |
behaviorConfig |
OtpBehaviorConfig |
OtpBehaviorConfig() |
Behavior configuration |
enableGradient |
bool |
false |
Enable gradient background |
gradientConfig |
OtpGradientConfig? |
null |
Gradient configuration |
enableHapticFeedback |
bool |
false |
Enable haptic feedback |
enableSoundFeedback |
bool |
false |
Enable sound feedback |
enableAutoSubmit |
bool |
false |
Enable auto submit when complete |
enableAutoClearOnError |
bool |
false |
Enable auto clear on error |
validationRegex |
String? |
null |
Validation regex pattern |
validationMessage |
String? |
null |
Custom validation message |
enableRealTimeValidation |
bool |
false |
Enable real-time validation |
enableScreenReaderSupport |
bool |
true |
Enable screen reader support |
Enums
OtpInputType
numeric
: Only accepts numbers (0-9)alphabetic
: Only accepts letters (a-z, A-Z)alphanumeric
: Accepts both letters and numberscustom
: Allows custom input with formatters
MaskingType
phone
: Masks phone numbers (e.g., 010******78)email
: Masks email addresses (e.g., u***@example.com)
OtpLayoutType
singleRow
: Fields arranged in a single horizontal rowwrap
: Fields wrap to next line when neededgrid
: Fields arranged in a gridcustom
: Custom layout using builder function
OtpFieldShape
rectangle
: Standard rectangle shaperoundedRectangle
: Rectangle with rounded cornerscircle
: Perfect circle shapestadium
: Pill-shaped (stadium) shapecustom
: Custom shape using path function
OtpFieldAlignment
start
: Align fields to the startcenter
: Center align fieldsend
: Align fields to the endspaceBetween
: Distribute fields with space betweenspaceAround
: Distribute fields with space aroundspaceEvenly
: Distribute fields with even spacing
OtpFieldDirection
horizontal
: Arrange fields horizontallyvertical
: Arrange fields vertically
CursorAlignment
left
: Align cursor to the leftcenter
: Center align cursor (default)right
: Align cursor to the right
Configuration Classes
OtpAnimationConfig
Predefined animation configurations:
OtpAnimationConfig.defaultConfig // Standard animations
OtpAnimationConfig.fast // Fast animations
OtpAnimationConfig.smooth // Smooth animations
OtpAnimationConfig.disabled // No animations
OtpThemeConfig
Theme configurations:
OtpThemeConfig.material3(context) // Material Design 3 theme
OtpThemeConfig.light // Light theme
OtpThemeConfig.dark // Dark theme
OtpBehaviorConfig
Behavior configuration options:
OtpBehaviorConfig(
enableHapticFeedback: true,
enableSoundFeedback: true,
enableAutoSubmit: true,
enableAutoClearOnError: true,
enableVisualFeedback: true,
)
OtpGradientConfig
Gradient configuration:
OtpGradientConfig(
colors: [Colors.blue, Colors.purple],
type: GradientType.linear,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
π¨ Examples
Basic OTP
OtpVerificationWidget(
title: 'Verify Phone Number',
subtitle: 'Enter the 5-digit code sent to {contactInfo}',
contactInfo: '01012345678',
maskingType: MaskingType.phone,
fieldCount: 5,
buttonText: 'Verify',
resendText: 'Resend Code',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Alphanumeric OTP
OtpVerificationWidget(
title: 'Verify Account',
subtitle: 'Enter the 6-character code',
fieldCount: 6,
otpInputType: OtpInputType.alphanumeric,
textCapitalization: TextCapitalization.characters,
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'expires in',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Secure OTP
OtpVerificationWidget(
title: 'Secure Verification',
subtitle: 'Enter the secure code',
fieldCount: 4,
obscureText: true,
obscuringCharacter: 'β',
enableShadow: true,
shadowColor: Colors.purple.withOpacity(0.3),
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Custom Validation
OtpVerificationWidget(
title: 'Custom Validation',
subtitle: 'Enter valid OTP',
fieldCount: 6,
validationRegex: r'^[0-9]{6}$',
validationMessage: 'Please enter 6 digits',
enableRealTimeValidation: true,
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Advanced Styling
OtpVerificationWidget(
title: 'Styled OTP',
subtitle: 'Enter code with style',
fieldCount: 5,
fieldShape: OtpFieldShape.circle,
fieldWidth: 60,
fieldHeight: 60,
borderRadius: 30,
primaryColor: Colors.purple,
enableGradient: true,
gradientConfig: OtpGradientConfig(
colors: [Colors.purple, Colors.pink],
type: GradientType.linear,
),
animationConfig: OtpAnimationConfig.smooth,
enableHapticFeedback: true,
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Cursor Control
OtpVerificationWidget(
title: 'Custom Cursor OTP',
subtitle: 'Enter code with custom cursor',
fieldCount: 6,
fieldHeight: 56,
// Cursor customization
cursorHeight: 50, // 6px padding from top and bottom (56 - 6 = 50)
cursorWidth: 2.0, // Thicker cursor
cursorAlignment: CursorAlignment.center, // Perfect center alignment
cursorColor: Colors.blue, // Blue cursor
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Enhanced Field Transitions
OtpVerificationWidget(
title: 'Enhanced Field Transitions',
subtitle: 'Smooth transitions and progressive highlighting',
fieldCount: 6,
fieldHeight: 60,
// Enhanced field transition features
fieldTransitionDuration: const Duration(milliseconds: 200),
fieldTransitionCurve: Curves.easeInOut,
enableFieldStateAnimation: true,
completedFieldBorderColor: Colors.green,
completedFieldBackgroundColor: Colors.green.withOpacity(0.1),
completedFieldTextColor: Colors.green,
enableProgressiveHighlighting: true,
enableFieldToFieldAnimation: true,
fieldToFieldTransitionDuration: const Duration(milliseconds: 150),
fieldToFieldTransitionCurve: Curves.easeInOut,
transitionHighlightColor: Colors.blue.withOpacity(0.3),
// Styling
primaryColor: Colors.blue,
focusedBorderColor: Colors.blue,
filledFieldBackgroundColor: Colors.blue.withOpacity(0.1),
fieldWidth: 50,
borderRadius: 12,
enableShadow: true,
shadowColor: Colors.blue.withOpacity(0.2),
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Complete Customization
OtpVerificationWidget(
title: 'Fully Customized OTP',
subtitle: 'Every aspect is controllable',
fieldCount: 6,
// Layout configuration
layoutType: OtpLayoutType.singleRow,
fieldAlignment: OtpFieldAlignment.center,
fieldShape: OtpFieldShape.roundedRectangle,
// Animation configuration
animationConfig: OtpAnimationConfig.smooth,
// Theme configuration
themeConfig: OtpThemeConfig.material3(context),
// Behavior configuration
behaviorConfig: OtpBehaviorConfig(
enableHapticFeedback: true,
enableSoundFeedback: true,
enableAutoSubmit: true,
enableAutoClearOnError: true,
),
// Responsive spacing
fieldSpacing: 12.0,
minFieldSpacing: 8.0,
maxFieldSpacing: 20.0,
// Gradient background
enableGradient: true,
gradientConfig: OtpGradientConfig(
colors: [Colors.blue, Colors.purple],
type: GradientType.linear,
),
// Advanced validation
enableRealTimeValidation: true,
validationRegex: r'^[0-9]{6}$',
validationMessage: 'Please enter 6 digits',
// Cursor control
cursorHeight: 50, // Perfect centering with 6px padding (56 - 12 = 44)
cursorWidth: 1.5, // Slightly thicker cursor
cursorAlignment: CursorAlignment.center, // Perfect center alignment
cursorColor: Colors.purple, // Purple cursor
// Accessibility
enableScreenReaderSupport: true,
semanticLabel: 'OTP verification code',
semanticHint: 'Enter the 6-digit code',
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
Error State Management
class MyOTPPage extends StatefulWidget {
@override
_MyOTPPageState createState() => _MyOTPPageState();
}
class _MyOTPPageState extends State<MyOTPPage> {
bool _hasError = false;
final GlobalKey<OtpVerificationWidgetState> _otpKey = GlobalKey();
void _handleVerification(String otp) {
// Simulate API call
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
final isSuccess = otp != '0000'; // Simulate failure for '0000'
setState(() {
_hasError = !isSuccess;
});
if (isSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('OTP verified successfully!')),
);
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: OtpVerificationWidget(
key: _otpKey,
title: 'Error State Demo',
subtitle: 'Try entering 0000 to see error state',
fieldCount: 4,
timerDuration: 60,
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
// Error state management
hasError: _hasError,
autoClearErrorOnInput: true,
autoClearErrorOnResend: true,
errorStateDuration: const Duration(seconds: 5),
onErrorStateChanged: () {
setState(() {
_hasError = false;
});
},
// Enhanced error styling
errorBorderColor: Colors.red,
completedFieldBorderColor: Colors.green,
completedFieldBackgroundColor: Colors.green.withOpacity(0.1),
completedFieldTextColor: Colors.green,
enableProgressiveHighlighting: true,
onVerify: _handleVerification,
onResend: () => print('Resend requested'),
),
),
);
}
}
Widget-Based Customization
OtpVerificationWidget(
title: 'Custom Widgets Demo',
subtitle: 'Fully customized with custom widgets',
fieldCount: 4,
timerDuration: 90,
buttonText: 'Verify',
resendText: 'Resend',
timerPrefix: 'after',
// Custom widgets
titleWidget: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple.shade400, Colors.pink.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Icon(Icons.security, size: 48, color: Colors.white),
const SizedBox(height: 8),
Text(
'Secure Verification',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
subtitleWidget: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.email, color: Colors.blue),
const SizedBox(width: 8),
Text(
'Code sent to your email',
style: TextStyle(
color: Colors.blue.shade700,
fontWeight: FontWeight.w600,
),
),
],
),
),
errorWidget: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.red),
const SizedBox(width: 8),
Text(
'Invalid code. Please try again.',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
],
),
),
verifyButtonWidget: Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.pink],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.purple.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: () => handleVerification(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
child: Text(
'Verify Code',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
resendWidget: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.refresh, size: 16, color: Colors.grey),
const SizedBox(width: 4),
TextButton(
onPressed: () => resendOtp(),
child: Text(
'Resend Code',
style: TextStyle(
color: Colors.purple,
fontWeight: FontWeight.w600,
),
),
),
],
),
onVerify: (otp) => handleVerification(otp),
onResend: () => resendOtp(),
)
π§ Public Methods
clearOtp()
Clears all OTP input fields and refocuses the first field.
final GlobalKey<OtpVerificationWidgetState> otpKey = GlobalKey();
// Clear OTP
otpKey.currentState?.clearOtp();
setOtp(String)
Pre-fills fields with provided OTP (useful for testing/auto-fill).
final GlobalKey<OtpVerificationWidgetState> otpKey = GlobalKey();
// Set OTP
otpKey.currentState?.setOtp('12345');
getCurrentOtp()
Returns the current OTP value from all input fields.
final GlobalKey<OtpVerificationWidgetState> otpKey = GlobalKey();
// Get current OTP
String currentOtp = otpKey.currentState?.getCurrentOtp() ?? '';
π― Best Practices
Performance
- Use
OtpAnimationConfig.disabled
for maximum performance - Set
enableShadow: false
if shadows are not needed - Use appropriate
fieldCount
for your use case (typically 4-6 digits)
Accessibility
- Always provide meaningful
semanticLabel
andsemanticHint
- Use
enableScreenReaderSupport: true
for accessibility - Ensure sufficient color contrast for all text and borders
Security
- Use
obscureText: true
for sensitive OTPs - Implement proper validation with
validationRegex
- Use
enableRealTimeValidation: true
for immediate feedback
Responsive Design
- Test on different screen sizes
- Use appropriate
minFieldSpacing
andmaxFieldSpacing
- Consider using
fieldSpacing
for consistent spacing
π Troubleshooting
Common Issues
Fields not appearing
- Check if
fieldCount
is greater than 0 - Ensure
fieldWidth
andfieldHeight
are positive values
Overflow errors
- The package automatically handles overflow with safety margins
- If issues persist, reduce
fieldWidth
or increaseminFieldSpacing
Validation not working
- Ensure
enableAutoValidation: true
- Check
validationRegex
pattern - Verify
validationMessage
is provided
Timer not showing
- Set
showTimer: true
- Ensure
timerDuration
is greater than 0
Debug Mode
Enable debug mode by setting enableAutoValidation: false
and handling validation manually.
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
π Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
π Links
Made with β€οΈ for the Flutter community
Libraries
- flutter_otp_kit
- A comprehensive Flutter package for OTP (One-Time Password) verification with customizable styling, localization support, and robust functionality.