Flutter OTP Widget

A highly customizable OTP (One-Time Password) text field widget for Flutter with support for multiple styles, themes, and input types.

Features

✨ Multiple Field Styles

  • Box (default) - Traditional bordered boxes
  • Underline - Minimalist underlined fields
  • Circle - Circular PIN-style fields

🎨 Flexible Theming

  • Light and dark theme presets
  • Fully customizable colors and dimensions
  • Automatic theme adaptation

⌨️ Input Type Control

  • Numbers only (default)
  • Text only (letters)
  • Alphanumeric (mixed)

πŸ”’ Secure Input

  • Password/PIN mode with obscured text
  • Customizable obscuring character

🎯 State Management

  • Error state styling
  • Disabled state
  • Auto-focus support
  • Focus and change callbacks

⚑ Smart Navigation

  • Auto-advance to next field
  • Backspace to previous field
  • Arrow key navigation
  • Automatic completion detection

Screenshots


Box Style

Underline Style

Circle Style

Secure PIN Entry

Error State

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  flutter_otp_widget: ^1.0.1

Then run:

flutter pub get

Basic Usage

import 'package:flutter_otp_widget/flutter_otp_widget.dart';

class OTPScreen extends StatefulWidget {
  @override
  _OTPScreenState createState() => _OTPScreenState();
}

class _OTPScreenState extends State<OTPScreen> {
  final controllers = List.generate(6, (_) => TextEditingController());
  final focusNodes = List.generate(6, (_) => FocusNode());

  @override
  void dispose() {
    for (var controller in controllers) {
      controller.dispose();
    }
    for (var node in focusNodes) {
      node.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: OTPTextField(
          controllers: controllers,
          focusNodes: focusNodes,
          textStyle: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          onCompleted: (otp) {
            print('OTP Entered: $otp');
            // Verify OTP
          },
          onChange: (value) {
            print('Current OTP: $value');
          },
        ),
      ),
    );
  }
}

Examples

Different Styles

// Box Style (Default)
OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  fieldStyle: OTPFieldStyle.box,
  textStyle: TextStyle(fontSize: 24),
)

// Underline Style
OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  fieldStyle: OTPFieldStyle.underline,
  textStyle: TextStyle(fontSize: 24),
)

// Circle Style
OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  fieldStyle: OTPFieldStyle.circle,
  textStyle: TextStyle(fontSize: 24),
)

Custom Theme

OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  theme: OTPTheme.custom(
    borderColor: Colors.grey,
    focusedBorderColor: Colors.blue,
    errorBorderColor: Colors.red,
    fillColor: Colors.white,
    borderRadius: BorderRadius.circular(12),
  ),
  textStyle: TextStyle(fontSize: 24),
)

Text Input

OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  inputType: OTPInputType.text,
  textCapitalization: TextCapitalization.characters,
  textStyle: TextStyle(fontSize: 24),
)

Secure PIN Entry

OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  fieldStyle: OTPFieldStyle.circle,
  obscureText: true,
  obscuringCharacter: '●',
  textStyle: TextStyle(fontSize: 24),
)

Error State

OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  hasError: isWrongOTP,
  theme: OTPTheme.light().copyWith(
    errorBorderColor: Colors.red,
  ),
  textStyle: TextStyle(fontSize: 24),
)

4-Digit Code

final controllers = List.generate(4, (_) => TextEditingController());
final focusNodes = List.generate(4, (_) => FocusNode());

OTPTextField(
  controllers: controllers,
  focusNodes: focusNodes,
  fieldStyle: OTPFieldStyle.underline,
  textStyle: TextStyle(fontSize: 24),
)

With Center Spacing (3-3 Layout)

OTPTextField(
  controllers: controllers, // 6 controllers
  focusNodes: focusNodes,
  centerSpacing: 16, // Extra space in the middle
  textStyle: TextStyle(fontSize: 24),
)
// Layout: XXX   XXX

Parameters

OTPTextField

Parameter Type Default Description
controllers List<TextEditingController> required Text controllers for each field
focusNodes List<FocusNode> required Focus nodes for each field
textStyle TextStyle required Text style for input
onCompleted ValueChanged<String>? null Callback when all fields are filled
onChange ValueChanged<String>? null Callback when any field changes
fieldWidth double 40 Width of each field
fieldHeight double 50 Height of each field
fieldSpacing double 8 Spacing between fields
centerSpacing double? null Extra spacing at the middle
theme OTPTheme? null Theme configuration
fieldStyle OTPFieldStyle box Visual style of fields
inputType OTPInputType number Type of input allowed
obscureText bool false Whether to obscure text
obscuringCharacter String '●' Character for obscuring
enabled bool true Whether fields are enabled
autoFocus bool false Auto-focus first field
hasError bool false Show error state
textCapitalization TextCapitalization none Text capitalization mode

See the API documentation for complete parameter list.

State Management Integration

The package works seamlessly with any state management solution. Here's an example with Riverpod:

@riverpod
class OtpControllers extends _$OtpControllers {
  @override
  List<TextEditingController> build(int count) {
    final controllers = List.generate(count, (_) => TextEditingController());

    ref.onDispose(() {
      for (var controller in controllers) {
        controller.dispose();
      }
    });

    return controllers;
  }

  void clear() {
    for (var controller in state) {
      controller.clear();
    }
  }
}

// Usage
final controllers = ref.watch(otpControllersProvider(6));

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

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

Issues and Feedback

Please file issues, bugs, or feature requests in our issue tracker.

Example App

For a complete example with all features demonstrated, check out the example directory.

Libraries

flutter_otp_widget