Library / SDK
Tkko/Flutter_Pinput avatar
Tkko/Flutter_Pinput

Flutter_Pinput treats a pin field as six themed states

Flutter package to create Pin code input text field with every pixel customization possibility 🎨 with beautiful animations, iOS autofill, Android autofill

859 stars217 forksDartMIT

At a glance

What is it?
Flutter_Pinput is a MIT licensed Dart package that renders a one time code entry field in Flutter, with a separate theme per interaction state, animation between states, form validation and SMS autofill. It is widely used and heavily customised, but its last published release is version 3.0.0 from 2023.
Who is it for?
Flutter_Pinput suits a Flutter team that needs a one time code field whose look is defined by the design system rather than by the platform default, and that wants the keyboard, cursor and obscuring behaviour under its own control. It is a poor fit for a team that needs an actively released package, since the newest tagged version is 3.0.0 from 2023-08-03 even though commits have landed more recently.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Six states, each with its own theme

The design idea at the centre of Flutter_Pinput is that a pin field is a small state machine, and each state gets its own visual treatment. The README names six of them: default, focused, submitted, following, disabled and error. You customize each one by passing the corresponding theme parameter, and the pin animates smoothly from one state to another automatically rather than snapping. That is the difference between this package and a row of text fields with a border. In a text field row, the focus indicator is drawn by the platform and the selection state is invisible, so a verification screen cannot visually distinguish the digit the user is about to change from the one they just entered. Here, the following state can be styled to look different from the focused state, which is what makes the field readable at a glance. The properties available on the theme are width, height, textStyle, margin, padding and constraints, with defaults of 56.0 for width and 60.0 for height.

The smallest useful usage is one widget

The default configuration is deliberately tiny, because the package assumes you will want to theme it later but does not force it. You construct the widget and give it a completion callback:

dart
Widget buildPinPut() {
  return Pinput(
    onCompleted: (pin) => print(pin),
  );
}

The callback fires when the last digit is entered. The widget name in the README is Pinput, and the package on pub.dev is pinput, so the import name and the repository name differ, which matters if you are copying identifiers from an issue thread. Beyond that the feature list is broader than the widget itself: animated decoration switching, form validation, SMS autofill on both iOS and Android, a standard cursor, a custom cursor, cursor animation, copy from clipboard, readiness for a custom keyboard, the standard paste option, an obscuring character, an obscuring widget, haptic feedback and closing the keyboard after completion. Those are the knobs that separate a polished OTP screen from a rushed one.

Theming starts from one PinTheme and branches with copyWith

Customisation follows a copy and override pattern, and the README shows the full chain. You define a base theme first:

dart
final defaultPinTheme = PinTheme(
  width: 56,
  height: 56,
  textStyle: TextStyle(fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600),
  decoration: BoxDecoration(
    border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
    borderRadius: BorderRadius.circular(20),
  ),
);

Then the other states derive from it, and the README offers two different copy helpers for two different jobs. copyDecorationWith replaces only the decoration, leaving other fields alone, which is what you want when the focused state differs only in its border colour and corner radius:

dart
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
  border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
  borderRadius: BorderRadius.circular(8),
);

final submittedPinTheme = defaultPinTheme.copyWith(
  decoration: defaultPinTheme.decoration.copyWith(
    color: Color.fromRGBO(234, 239, 243, 1),
  ),
);

copyWith is the general case, where you replace a field and provide the new decoration yourself. If you want all pins to look identical, the README notes that you simply do not pass the other theme parameters.

Validation is a validator function plus an autovalidate mode

Form validation is wired into the widget rather than left to a surrounding Form. The assembled example in the README shows the pattern, where a validator function returns null when the value is acceptable and a message string when it is not, and a mode setting decides when that check runs:

dart
return Pinput(
defaultPinTheme: defaultPinTheme,
focusedPinTheme: focusedPinTheme,
submittedPinTheme: submittedPinTheme,
validator: (s) {
return s == '2222' ? null : 'Pin is incorrect';
},
pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
showCursor: true,
onCompleted: (pin) => print(pin),
);

The PinputAutovalidateMode setting is the part to notice. With onSubmit, the error state appears when the user finishes entering the code, not on every keystroke, which is the behaviour you want for a code that is not valid until it has enough digits. Combined with the error theme, the error state changes the colour of the offending digit. The validator signature also tells you something about the API contract: it receives the entered string, not a list of digits, so a validator that needs per-digit rules has to parse it.

iOS autofill is native, Android autofill is your auth provider's job

The SMS autofill story differs sharply between the two platforms, and the README is explicit about it. On iOS it works out of the box, by tapping the code on top of the keyboard. On Android, if you are using firebase_auth, you have to set the controller's value in the verificationCompleted callback, so the package holds a controller you own and your authentication layer fills it:

dart
    Pinput(
      controller: pinController,
    );

and then in the Firebase flow:

dart
    await FirebaseAuth.instance.verifyPhoneNumber(
      verificationCompleted: (PhoneAuthCredential credential) {
        pinController.setText(credential.smsCode);
      },
      verificationFailed: (FirebaseAuthException e) {},
      codeSent: (String verificationId, int? resendToken) {},
      codeAutoRetrievalTimeout: (String verificationId) {},
    );

So on Android the package is a destination, not a participant. The repository topics reflect the same split, listing the SMS retriever API, the SMS user consent API, android autofill and ios autofill separately. Any evaluation of autofill support has to be platform specific, and the Android path cannot work until your own auth layer hands the code over.

A widely used package whose last release is from 2023

Adoption reality and release reality are two different things here, and both are worth knowing. The package is MIT licensed and published on pub.dev as pinput, with a homepage, a live demo linked from the README, a Discord channel, and an example application in the repository that carries templates for android, ios, linux, macos, web and windows and multiple demonstration images, so you can see the visual options without reading the code. The release list is less current. The most recent tagged versions are v3.0.0 from 2023-08-03, v2.3.0 from 2023-07-24 and v2.2.13 from 2022-10-03, and the repository also carries a MIGRATION.md file, which is what you would expect when a 3.x line broke compatibility with 2.x. The last push to the repository was on 2026-10-01, so work is still landing in the tree even though the published package has not been tagged since 2023. That gap is the main risk in adopting it, and the CHANGELOG.md in the root is where you would look to see what has changed since the tag you depend on.

Editorial conclusion

Flutter_Pinput suits a Flutter team that needs a one time code field whose look is defined by the design system rather than by the platform default, and that wants the keyboard, cursor and obscuring behaviour under its own control. It is a poor fit for a team that needs an actively released package, since the newest tagged version is 3.0.0 from 2023-08-03 even though commits have landed more recently. Verify first that the API in the README matches the version you pull from pub.dev, read MIGRATION.md before upgrading from a 2.x line, and confirm on device that SMS autofill works on the Android path, since that path depends on your auth provider filling the controller rather than the package doing it.

Frequently asked questions

How do I add a pin code field in Flutter with Flutter_Pinput?

Create the Pinput widget and pass an onCompleted callback that receives the entered code. The widget takes a defaultPinTheme and per state themes such as focusedPinTheme, submittedPinTheme, followingPinTheme and errorPinTheme, plus an optional validator.

What are the six pin states in Flutter_Pinput?

They are default, focused, submitted, following, disabled and error. Each one can have its own theme, and the pin animates smoothly from one state to another automatically.

How does SMS autofill work with Flutter_Pinput on Android?

It is not automatic. If you use firebase_auth you must pass a controller to the Pinput widget and set the controller's value in the verificationCompleted callback with pinController.setText(credential.smsCode). On iOS the autofill works out of the box by tapping the code above the keyboard.

How do I validate the code the user typed with Flutter_Pinput?

Pass a validator that returns null for an acceptable value or an error message otherwise, and set pinputAutovalidateMode to control when the check runs. PinputAutovalidateMode.onSubmit runs the validation when the entry is complete rather than on every keystroke.

What is the latest release of the pinput package?

The most recent tagged release is v3.0.0 from 2023-08-03, before that v2.3.0 from 2023-07-24 and v2.2.13 from 2022-10-03. The repository has a MIGRATION.md file for moving between the 2.x and 3.x APIs.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. Tkko/Flutter_Pinput on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tkko-flutter-pinput.svg)](https://hysenlabs.com/projects/tkko-flutter-pinput)

Was this useful?