Onscreen Keypad

A customizable on-screen numeric keypad for Flutter applications — built for PIN entry, authentication screens, and custom input fields.

  • Shuffled keys to defend PIN entry against shoulder-surfing and touch-trace attacks
  • A controller that tracks the entry for you and reports completion
  • PinIndicator dots that follow the same controller
  • Theming through KeypadTheme, with dark-mode-aware defaults
  • Haptic feedback, configurable per key press

Installation

Using flutter pub add

flutter pub add onscreen_keypad

Or manually add it to pubspec.yaml

dependencies:
  onscreen_keypad: ^1.1.0

Then run:

flutter pub get

Usage

import 'package:onscreen_keypad/onscreen_keypad.dart';

PIN entry with a controller

The keypad maintains the entered value for you and calls onCompleted once maxLength is reached.

class PinScreen extends StatefulWidget {
  const PinScreen({super.key});

  @override
  State<PinScreen> createState() => _PinScreenState();
}

class _PinScreenState extends State<PinScreen> {
  final KeypadController _controller = KeypadController(maxLength: 4);

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _verify(String pin) {
    // ...
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          PinIndicator(controller: _controller),
          OnScreenKeyPad(
            controller: _controller,
            onCompleted: _verify,
          ),
        ],
      ),
    );
  }
}

KeypadController is a ChangeNotifier, so anything can listen to it — controller.value, .length, .isComplete, .clear() and .backspace() are all available directly.

Raw callback

If you would rather manage the value yourself, pass onKeyPress. It receives the digit that was pressed, or 'backspace'.

OnScreenKeyPad(
  onKeyPress: (value) {
    if (value == 'backspace') {
      // remove the last character
    } else {
      // append value
    }
  },
)

Both can be supplied together: onKeyPress fires for every press while the controller keeps the value in sync.

Shuffled keys

A fixed keypad layout lets an onlooker recover a PIN from finger positions alone. Setting shuffle: true randomizes the digits while leaving the action slots in place, so backspace and submit stay where users expect them.

OnScreenKeyPad(
  controller: _controller,
  onCompleted: _verify,
  shuffle: true,
  reshuffleMode: ReshuffleMode.everyEntry,
)
ReshuffleMode Keys rearrange
once (default) when the keypad is first built
everyKey after every key press
everyEntry each time an entry completes — needs a controller with a maxLength
manual only when controller.shuffleKeys() is called

Digits are drawn from Random.secure(). Pass random: a seeded Random to make the layout deterministic in tests.

Theming

Set the styling once for every keypad below it:

KeypadTheme(
  data: KeypadThemeData(
    keyShape: BoxShape.circle,
    keyPadding: const EdgeInsets.all(20),
    keyTextStyle: const TextStyle(fontSize: 32),
    keySplashColor: Colors.indigo.shade100,
  ),
  child: OnScreenKeyPad(controller: _controller),
)

Resolution order for every visual property is widget argument → KeypadTheme → ambient ThemeData. Anything left unset follows the app's ColorScheme, so the keypad is legible in dark mode without configuration. A style passed to the widget is merged over the themed one rather than replacing it, so setting only a font size keeps the themed color.

Haptic feedback

OnScreenKeyPad(
  controller: _controller,
  enableHapticFeedback: true,
  hapticFeedbackType: HapticFeedbackType.selectionClick,
)

Defaults to HapticFeedbackType.lightImpact. Flutter's engine only exposes impact and selection haptics, so notificationSuccess, notificationWarning and notificationError are approximated with the closest impact available.

Action keys

leftButtonAction replaces the default backspace key; rightButtonAction fills the otherwise-empty bottom-right slot — a natural home for a biometrics button. Both are rendered as given, so they are responsible for their own styling and callbacks.

OnScreenKeyPad(
  controller: _controller,
  rightButtonAction: IconButton(
    icon: const Icon(Icons.fingerprint),
    iconSize: 36,
    onPressed: _authenticateWithBiometrics,
  ),
)

API reference

OnScreenKeyPad

Property Type Default Description
controller KeypadController? null Tracks the entered value and drives onCompleted
onKeyPress void Function(String)? null Fires on every press with the digit or 'backspace'
onCompleted void Function(String)? null Fires when the controller reaches its maxLength
shuffle bool false Randomizes the digit positions
reshuffleMode ReshuffleMode once How often a shuffled keypad rearranges
random Random? Random.secure() Random source for shuffling
enableHapticFeedback bool false Enables haptics on key press
hapticFeedbackType HapticFeedbackType? lightImpact Which haptic to trigger
keyPadding EdgeInsets? EdgeInsets.symmetric(vertical: 16, horizontal: 16) Padding inside each key
keyShape BoxShape? BoxShape.rectangle Shape of the keys
keyCellBackgroundColor Color? colorScheme.surface Key background
keyCellSplashColor Color? transparent Splash on tap
keyCellHighlightColor Color? transparent Highlight on tap
keyIconColor Color? colorScheme.onSurface Color of icon keys
style TextStyle? size 40, colorScheme.onSurface Key label style
leftButtonAction Widget? backspace key Bottom-left slot
rightButtonAction Widget? empty Bottom-right slot

At least one of controller or onKeyPress must be provided.

KeypadController

value, length, isEmpty, isNotEmpty, isComplete, append, backspace, clear, shuffleKeys. Dispose it where you create it.

Example

A complete example lives in example/main.dart.

License

MIT — see LICENSE.

Libraries

flutter_keypad_package
Legacy entry point. Prefer package:onscreen_keypad/onscreen_keypad.dart.
on_screen_keypad
Legacy entry point. Prefer package:onscreen_keypad/onscreen_keypad.dart.
onscreen_keypad
A customizable on-screen numeric keypad for Flutter applications.