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.
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.