Installation
composer require hasan-ahani/filament-otp-input
Publish the config (if needed):
php artisan vendor:publish --provider="HasanAhan\FilamentOtpInput\FilamentOtpInputServiceProvider"
Register the Component Add the component to your Filament form:
use HasanAhan\FilamentOtpInput\Components\OtpInput;
OtpInput::make('otp_code')
->label('One-Time Passcode')
->required()
->live() // Optional: Validate in real-time
->rules(['required', 'digits:6']) // Custom validation rules
First Use Case Use it in a Filament Form (e.g., login, password reset, or 2FA verification):
public static function form(Form $form): Form
{
return $form
->schema([
OtpInput::make('verification_code')
->label('Enter OTP')
->maxLength(6)
->onChange(function (Form $form, string $value) {
// Trigger action (e.g., verify OTP)
$this->verifyOtp($value);
}),
]);
}
Basic OTP Input
OtpInput::make('otp')
->numberOfInputs(6) // Default: 6
->autofocus() // Auto-focus first input
->placeholder('0') // Placeholder for each digit
Dynamic Actions
->onChange(function (Form $form, string $value) {
if ($value->length === 6) {
$form->submit();
}
})
public function submitOtp(Form $form)
{
$otp = $form->get('otp');
// Verify OTP with your service (e.g., Laravel Notifications)
}
Integration with Filament Features
->rules(['required', 'digits:6', 'otp_verifiable']) // Custom rule
OtpInput::make('otp')
->visible(fn (Form $form) => $form->get('enable_otp'))
Custom Styling Override Blade templates or use Filament’s CSS utilities:
->extraAttributes(['class' => 'bg-gray-50 border-gray-300'])
Resend OTP Logic Combine with a button to resend OTP:
use Filament\Forms\Components\Actions\Action;
Action::make('resend_otp')
->label('Resend OTP')
->action(function (Form $form) {
$this->sendOtp($form->get('email'));
$form->fill('otp', ''); // Clear input
}),
Multi-Step Forms Use in a Filament Multi-Step Form for progressive OTP verification:
public static function form(Form $form): Form
{
return $form
->schema([
// Step 1: Email input
TextInput::make('email')->required(),
// Step 2: OTP input (conditional)
OtpInput::make('otp')
->visibleOn('email')
->afterStateUpdated(function (Form $form) {
$form->moveToStep(3); // Proceed to next step
}),
]);
}
Localization Translate labels/placeholders:
->label(__('auth.otp_code'))
->placeholder(__('auth.digit_placeholder'))
Auto-Submit Conflicts
onChange triggers submission, ensure the form’s submit action is idempotent.->defer() or debounce the action:
->onChange(fn ($value) => $this->verifyOtpLater($value))
Validation Timing
->live()) may fire before the OTP is fully entered.->afterStateUpdated() for post-submission validation:
->afterStateUpdated(fn ($state) => $this->validateOtp($state))
Mobile Input Issues
->extraAttributes(['data-mobile-friendly' => 'true'])
CSRF Token Refresh
->preserveCsrfToken().Log OTP State
Use ->afterStateUpdated() to debug:
->afterStateUpdated(fn ($state) => \Log::info('OTP entered:', ['otp' => $state]))
Check JavaScript Events
Inspect browser console for errors (e.g., Uncaught TypeError if onChange misfires).
Clear Cache If styles/behavior break after updates:
php artisan view:clear
php artisan filament:cache-reset
Custom Validation Rules Create a reusable rule for OTP verification:
use Illuminate\Validation\Rule;
Rule::macro('otpVerifiable', function ($otp) {
return function ($attribute, $value, $fail) {
if (!$this->otpService->verify($value, $otp)) {
$fail('Invalid OTP.');
}
};
});
Then use:
->rules(['required', 'digits:6', Rule::otpVerifiable($user->otp)])
Override Templates Publish and modify the Blade view:
php artisan vendor:publish --tag=filament-otp-input-views
Edit resources/views/vendor/filament-otp-input/otp-input.blade.php.
Add Haptic Feedback Enhance UX with JavaScript:
document.querySelectorAll('.filament-otp-input input').forEach(input => {
input.addEventListener('input', () => {
input.setAttribute('data-haptic', 'success');
});
});
Accessibility Add ARIA labels for screen readers:
->extraAttributes([
'aria-label' => 'One-Time Passcode',
'role' => 'textbox',
])
How can I help you explore Laravel packages today?