onscreen_keypad 1.1.0
onscreen_keypad: ^1.1.0 copied to clipboard
A customizable on-screen numeric keypad for Flutter apps. Ideal for PIN entry, authentication screens, and custom input fields with haptic feedback support.
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
PinIndicatordots 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.