flutter_pinput 2.0.0
flutter_pinput: ^2.0.0 copied to clipboard
Pin code input (OTP) text field, iOS SMS autofill, Android SMS autofill One Time Code, Password, Passcode, Captcha, Security, Coupon, Wowcher, 2FA, Two step verification
Flutter Pinput is an easy-to-use and customizable pin code input field. It supports animated decoration switching, form validation, SMS autofill, custom cursor, copying from clipboard and more. It also comes with beautiful examples you can choose from.
Features #
- Animated decoration switching
- Form validation
- SMS autofill on iOS
- SMS autofill on Android
- Standard cursor
- Custom cursor
- Cursor animation
- Copy from clipboard
- Ready for custom keyboard
- Standard paste option
- Obscuring character
- Obscuring widget
- Haptic feedback
- Close keyboard after completion
- Beautiful examples
Installation #
Requires Flutter >=3.47.0 (Dart >=3.13.0).
dependencies:
flutter_pinput: ^2.0.0
material_ui: ^1.5.0
import 'package:flutter_pinput/flutter_pinput.dart';
import 'package:material_ui/material_ui.dart';
Migrating to 2.0.0 #
flutter_pinput 2.0.0 is built on the decoupled material_ui
and cupertino_ui packages. They are separate copies of
package:flutter/material.dart, so Pinput only finds the Material, Theme and localizations
that come from material_ui.
- Upgrade Flutter to
3.47or newer. - Add
material_uito yourpubspec.yamland changeimport 'package:flutter/material.dart';toimport 'package:material_ui/material_ui.dart';(see the material_ui migration guide). - If some of your dependencies still use
package:flutter/material.dart, wrap your app withMaterialUiCompatibilityBridgeviaMaterialApp.builder. - Remove
hintLocalesif you passed it toPinput.
Can't upgrade yet? Stay on flutter_pinput: 1.0.3.
Demo #
| Live Demo | Rounded With Shadows | Rounded With Cursor |
|---|---|---|
![]() |
![]() |
![]() |
| Rounded Filled | With Bottom Cursor | Filled |
|---|---|---|
![]() |
![]() |
![]() |
Getting Started #
A pin has 6 states: default, focused, submitted, following, disabled and error.
You can customize each state with its own PinTheme. Pin smoothly animates from one state to
another automatically.
PinTheme #
| Property | Type | Default |
|---|---|---|
| width | double |
56.0 |
| height | double |
60.0 |
| textStyle | TextStyle |
— |
| decoration | BoxDecoration |
— |
| margin | EdgeInsetsGeometry |
— |
| padding | EdgeInsetsGeometry |
— |
| constraints | BoxConstraints |
— |
Standard Pinput:
Widget buildPinput() {
return Pinput(
onCompleted: (pin) => debugPrint(pin),
);
}
To customize it, create defaultPinTheme first:
const defaultPinTheme = PinTheme(
width: 56,
height: 56,
textStyle: TextStyle(
fontSize: 20,
color: Color.fromRGBO(30, 60, 87, 1),
fontWeight: FontWeight.w600,
),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(color: Color.fromRGBO(234, 239, 243, 1)),
),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
);
If you want all pins to look the same, don't pass other themes. Otherwise create
focusedPinTheme, submittedPinTheme, followingPinTheme or errorPinTheme from
defaultPinTheme:
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
border: Border.all(color: const Color.fromRGBO(114, 178, 238, 1)),
borderRadius: BorderRadius.circular(8),
);
final submittedPinTheme = defaultPinTheme.copyDecorationWith(
color: const Color.fromRGBO(234, 239, 243, 1),
);
Put everything together:
Widget buildPinput() {
const defaultPinTheme = PinTheme(
width: 56,
height: 56,
textStyle: TextStyle(
fontSize: 20,
color: Color.fromRGBO(30, 60, 87, 1),
fontWeight: FontWeight.w600,
),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(color: Color.fromRGBO(234, 239, 243, 1)),
),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
);
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
border: Border.all(color: const Color.fromRGBO(114, 178, 238, 1)),
borderRadius: BorderRadius.circular(8),
);
final submittedPinTheme = defaultPinTheme.copyDecorationWith(
color: const Color.fromRGBO(234, 239, 243, 1),
);
return Pinput(
defaultPinTheme: defaultPinTheme,
focusedPinTheme: focusedPinTheme,
submittedPinTheme: submittedPinTheme,
validator: (pin) => pin == '2222' ? null : 'Pin is incorrect',
pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
showCursor: true,
onCompleted: (pin) => debugPrint(pin),
);
}
SMS Autofill #
iOS #
Works out of the box — tap the code shown above the keyboard.
Android #
With firebase_auth
If you are using firebase_auth,
set the controller's value in the verificationCompleted callback:
final pinController = TextEditingController();
Pinput(
controller: pinController,
);
await FirebaseAuth.instance.verifyPhoneNumber(
verificationCompleted: (PhoneAuthCredential credential) {
pinController.setText(credential.smsCode!);
},
verificationFailed: (FirebaseAuthException e) {},
codeSent: (String verificationId, int? resendToken) {},
codeAutoRetrievalTimeout: (String verificationId) {},
);
Without firebase_auth
You have two options: SMS Retriever API and SMS User Consent API. SmartAuth is a Flutter wrapper for both — add it as a dependency.
SMS Retriever API
Requires the app signature — see this guide.
Note: the app signature may differ between debug and release builds.
Include the app signature in the SMS your backend sends:
Your ExampleApp code is: 123456
kg+TZ3A5qzS
The code is applied automatically, without user interaction. Example code
SMS User Consent API
No app signature needed — the user is prompted to allow reading the message. Example code
Tips #
Controller #
// Create controller
final pinController = TextEditingController();
// Set text programmatically
pinController.setText('1222');
// Append typed character, useful with a custom keyboard.
// Second argument is the pin length.
pinController.append('1', 4);
// Delete last character
pinController.delete();
// Don't call setText, append or delete in build method — this is just an illustration.
return Pinput(
controller: pinController,
);
Focus #
// Create focus node
final pinputFocusNode = FocusNode();
// Focus Pinput
pinputFocusNode.requestFocus();
// Unfocus Pinput
pinputFocusNode.unfocus();
// Don't call requestFocus or unfocus in build method — this is just an illustration.
return Pinput(
focusNode: pinputFocusNode,
);
Validation #
// Create key
final formKey = GlobalKey<FormState>();
// Validate manually.
// Don't call validate in build method — this is just an illustration.
formKey.currentState!.validate();
return Form(
key: formKey,
child: Pinput(
// --- Without validator ---
// If true, error state is applied no matter what validator returns.
forceErrorState: true,
// Displayed under the Pinput.
errorText: 'Error',
// --- With validator ---
// Validates after the user taps the keyboard done button or completes the Pinput.
pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
validator: (pin) {
if (pin == '2224') return null;
// Displayed under the Pinput.
return 'Pin is incorrect';
},
),
);
FAQ #
Autofill isn't working on iOS?
- Make sure you are using a real device, not a simulator.
- Temporarily replace
PinputwithTextFieldand check if autofill works. If not, the problem is probably the SMS you are receiving — autofill doesn't work with most languages. - If you are on a non-stable Flutter channel, something might be broken inside the framework.
Using firebase_auth?
See With firebase_auth.
Support #
PRs welcome! Check the example app for more templates.
Don't forget to give it a star ⭐





