Skip to main content

otpin 0.1.0 copy "otpin: ^0.1.0" to clipboard
otpin: ^0.1.0 copied to clipboard

A beautifully animated, customizable OTP input widget for Flutter. Ships with Orbital and Cascade visual styles — each with rich animations for empty, typing, verifying, success, and error states. Zer [...]

OTPin Logo

OTPin

Beautifully animated OTP input for Flutter

pub version build license platform


OTPin is a plug-and-play OTP (One-Time Password) input widget for Flutter that ships with multiple, richly animated visual styles. Each style supports five animated states — Empty, Typing, Verifying, Success, and Error — and is fully customizable.

Zero external dependencies. Built entirely with Flutter's built-in animation system.


✨ Styles #

Orbital Style #

Digit fields orbit in a circle during verification, converge to a success icon on success, and shake on error.

Orbital Style Demo

Cascade Style #

A cascading sinusoidal wave ripples through fields during verification. On success, fields morph into a unified capsule with a checkmark. On error, a staggered domino shake effect plays.

Cascade Style Demo


🚀 Installation #

Add otpin to your pubspec.yaml:

dependencies:
  otpin: ^0.1.0

Then run:

flutter pub get

📖 Quick Start #

import 'package:otpin/otpin.dart';

OTPin(
  length: 4,
  style: OrbitalStyle(),
  onCompleted: (code) async {
    final isValid = await verifyOTP(code);
    return isValid; // true → success animation, false → error animation
  },
)

That's it — a fully animated OTP input in 3 lines.


🎨 Styles & Usage #

Orbital Style #

OTPin(
  length: 4,
  style: OrbitalStyle(
    config: OrbitalStyleConfig(
      // ─── Colors ───
      focusedBorderColor: Colors.cyan,
      filledBorderColor: Colors.cyanAccent,
      emptyBorderColor: Color(0xFF25334D),
      fieldBackgroundColor: Color(0xFF141C2E),
      cursorColor: Colors.cyanAccent,
      successColor: Color(0xFF00E676),
      errorColor: Color(0xFFFF5252),

      // ─── Success Display ───
      successIcon: Icons.check_rounded,
      successIconColor: Colors.white,

      // ─── Sizing ───
      fieldSize: 60.0,
      fieldBorderRadius: 16.0,
      fieldSpacing: 12.0,
      orbitRadius: 80.0,
      orbitFieldScale: 0.7,

      // ─── Durations ───
      orbitRevolutionDuration: Duration(seconds: 3),
      successConvergeDuration: Duration(milliseconds: 600),
      errorShakeDuration: Duration(milliseconds: 500),
    ),
  ),
  onCompleted: (code) async => await api.verify(code),
  onChanged: (code) => debugPrint('Current: $code'),
)

Orbital Animation States

State Animation
Empty Subtle ambient pulse on all fields
Typing Blinking cursor on focused field, scale-in on digit entry
Verify Fields shrink and orbit in a circle with a guide ring
Success Fields converge to center, revealing a success icon circle
Error Horizontal shake with red flash, then auto-clear

Cascade Style #

OTPin(
  length: 4,
  style: CascadeStyle(
    config: CascadeStyleConfig(
      // ─── Colors ───
      focusedBorderColor: Color(0xFF3B82F6),
      filledBorderColor: Color(0xFF2563EB),
      emptyBorderColor: Color(0xFF25334D),
      fieldBackgroundColor: Color(0xFF141C2E),
      cursorColor: Color(0xFF60A5FA),
      waveBeamColor: Color(0xFF60A5FA),
      successColor: Color(0xFF00E676),
      errorColor: Color(0xFFFF5252),

      // ─── Success Display ───
      successIcon: Icons.check_rounded,
      successText: 'Verified',

      // ─── Sizing ───
      fieldSize: 60.0,
      fieldBorderRadius: 16.0,
      fieldSpacing: 12.0,
      waveHeight: 14.0,

      // ─── Durations ───
      waveCycleDuration: Duration(milliseconds: 1200),
      morphDuration: Duration(milliseconds: 500),
      errorShakeDuration: Duration(milliseconds: 500),
    ),
  ),
  onCompleted: (code) async => await api.verify(code),
)

Cascade Animation States

State Animation
Empty Subtle ambient pulse on all fields
Typing Blinking cursor on focused field, pop-in on digit entry
Verify Sinusoidal cascading wave with shimmer beam
Success Fields morph into a unified capsule with checkmark + label
Error Staggered domino shake with red flash, then auto-clear

🔢 Configurable Length #

Control the number of OTP digit inputs with the length parameter (works with all styles):

// 6-digit OTP
OTPin(
  length: 6,
  style: OrbitalStyle(),
  onCompleted: (code) async => await api.verify(code),
)

// 8-digit OTP with Cascade style
OTPin(
  length: 8,
  style: CascadeStyle(),
  onCompleted: (code) async => await api.verify(code),
)

When using an external controller, make sure the controller length matches:

final controller = OTPinController(length: 6);

OTPin(
  length: 6,
  style: CascadeStyle(),
  controller: controller,
  onCompleted: (code) async => await api.verify(code),
)

🎮 Programmatic Control #

Use OTPinController for full programmatic access:

final controller = OTPinController(length: 4);

// Use the controller with the widget.
OTPin(
  length: 4,
  style: OrbitalStyle(),
  controller: controller,
  onCompleted: (code) async => await api.verify(code),
)

// Set digits programmatically (e.g. from clipboard).
controller.setText('1234');

// Clear all digits.
controller.clear();

// Manually trigger states for testing.
controller.triggerSuccess();
controller.triggerError();

🏗️ Architecture #

OTPin uses the Strategy Pattern to cleanly decouple visual styles from input logic:

┌─────────────────────────────────────────┐
│                  OTPin                  │  ← Main widget
│  ┌──────────────┐  ┌────────────────┐   │
│  │ OTPinController│  │  OTPinStyle   │   │  ← Input logic + Visual strategy
│  └──────────────┘  └───────┬────────┘   │
└─────────────────────────────┼───────────┘
                              │
              ┌───────────────┼───────────────┐
              │               │               │
     ┌────────┴────┐  ┌──────┴──────┐  ┌─────┴─────┐
     │ OrbitalStyle│  │CascadeStyle │  │ YourStyle │
     └─────────────┘  └─────────────┘  └───────────┘

Key Components #

Component Responsibility
OTPin Composes controller + style, manages hidden keyboard
OTPinController Digit state, focus index, state machine transitions
OTPinState Enum: empty, typing, verifying, success, error
OTPinStyle Abstract interface — implement to create new styles
OrbitalStyle Orbital orbit/converge animation style
CascadeStyle Cascading wave/morph animation style

🧩 Creating a Custom Style #

Implement OTPinStyle to create your own animated OTP input:

import 'package:otpin/otpin.dart';

class MyCustomStyle implements OTPinStyle {
  @override
  Widget build(
    BuildContext context, {
    required OTPinController controller,
    required FocusNode focusNode,
  }) {
    return ListenableBuilder(
      listenable: controller,
      builder: (context, _) {
        // Build your UI based on controller.state,
        // controller.digits, controller.focusedIndex, etc.
        return Row(
          children: List.generate(controller.length, (i) {
            return _buildField(controller, i);
          }),
        );
      },
    );
  }

  Widget _buildField(OTPinController controller, int index) {
    final digit = controller.digits[index];
    final isFocused = controller.focusedIndex == index;
    // ... your custom rendering
  }
}

Then use it:

OTPin(
  length: 6,
  style: MyCustomStyle(),
  onCompleted: (code) async => await api.verify(code),
)

⚙️ Configuration Reference #

OrbitalStyleConfig #

Property Type Default Description
focusedBorderColor Color Color(0xFF3B82F6) Border color of focused field
filledBorderColor Color Color(0xFF2563EB) Border color of filled field
emptyBorderColor Color Color(0xFF25334D) Border color of empty field
fieldBackgroundColor Color Color(0xFF141C2E) Field background color
successColor Color Color(0xFF00E676) Success state accent color
errorColor Color Color(0xFFFF5252) Error state accent color
successIcon IconData Icons.check_rounded Icon shown on success
successChild Widget? null Custom widget instead of icon
fieldSize double 60.0 Width/height of each field
fieldBorderRadius double 16.0 Corner radius
orbitRadius double 80.0 Radius of orbit circle
orbitRevolutionDuration Duration 2500ms Time for one full orbit

CascadeStyleConfig #

Property Type Default Description
focusedBorderColor Color Color(0xFF3B82F6) Border color of focused field
filledBorderColor Color Color(0xFF2563EB) Border color of filled field
emptyBorderColor Color Color(0xFF25334D) Border color of empty field
fieldBackgroundColor Color Color(0xFF141C2E) Field background color
waveBeamColor Color Color(0xFF60A5FA) Shimmer beam color during verify
successColor Color Color(0xFF00E676) Success state accent color
errorColor Color Color(0xFFFF5252) Error state accent color
successIcon IconData Icons.check_rounded Icon shown on success
successText String? 'Verified' Label beside checkmark
fieldSize double 60.0 Width/height of each field
fieldBorderRadius double 16.0 Corner radius
waveHeight double 14.0 Peak wave oscillation height
waveCycleDuration Duration 1200ms Duration of one wave cycle
morphDuration Duration 500ms Capsule morph animation duration

📋 API Reference #

OTPin Widget #

const OTPin({
  int length = 4,             // Number of digits
  required OTPinStyle style,  // Visual style to render
  OTPinController? controller,// Optional external controller
  Future<bool> Function(String)? onCompleted, // Verification callback
  ValueChanged<String>? onChanged,            // Digit change callback
  bool autoFocus = true,      // Auto-focus on mount
})

OTPinController #

class OTPinController extends ChangeNotifier {
  OTPinController({required int length});

  List<String> get digits;       // Current digit values
  OTPinState get state;          // Current animation state
  int get focusedIndex;          // Currently focused field
  bool get isFilled;             // Whether all digits are entered
  String get text;               // Current OTP string

  void addDigit(String digit);   // Add a single digit
  void removeLastDigit();        // Remove last digit
  void setText(String value);    // Set all digits at once
  void clear();                  // Clear all digits
  void startVerifying();         // Transition to verifying state
  void triggerSuccess();         // Transition to success state
  void triggerError();           // Transition to error state
}

OTPinState #

enum OTPinState {
  empty,      // No digits entered — ambient pulse
  typing,     // User is entering digits
  verifying,  // Code submitted — loading animation
  success,    // Verification passed
  error,      // Verification failed
}

🤝 Contributing #

Contributions are welcome! Feel free to:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-style)
  3. Implement your changes
  4. Add tests
  5. Submit a Pull Request

📄 License #

This project is licensed under the MIT License — see the LICENSE file for details.


Made with ❤️ by Abdelhaliem

3
likes
150
points
363
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A beautifully animated, customizable OTP input widget for Flutter. Ships with Orbital and Cascade visual styles — each with rich animations for empty, typing, verifying, success, and error states. Zero dependencies. Strategy Pattern architecture.

Repository (GitHub)
View/report issues

Topics

#otp #widget #animation #input #verification

License

MIT (license)

Dependencies

flutter

More

Packages that depend on otpin