virtual_keypad v1.1.1
pub.dev GitHub

Use any TextField

One flag, no wrapper widgets, and the form you already wrote stays as it is.

Install#

flutter pub add virtual_keypad
import 'package:virtual_keypad/virtual_keypad.dart';

Standalone mode#

Column(
  children: [
    TextField(controller: emailController),
    TextField(controller: passwordController),
    VirtualKeypad(standalone: true),
  ],
)

The keyboard watches focus and types into whichever field has it. Your controllers, validators and onChanged callbacks all keep working, because the text goes in through the normal editing path rather than around it.

The layout follows the field#

Each field's keyboardType picks the layout, so you do not configure the keyboard per field.

FieldLayout
TextInputType.emailAddressQWERTY with @ and . on the main page
TextInputType.urlQWERTY with /, : and .
TextInputType.numberNumeric pad
TextInputType.phoneDialer
TextInputType.multilineQWERTY with a newline key

Pass type: on the keypad to override that and pin one layout for every field.

Keeping the system keyboard shut#

On a phone or tablet both keyboards would otherwise appear. Standalone mode suppresses the system one for the fields it drives, so you do not need readOnly: true or a focus node that refuses focus. Those tricks also kill the caret and selection, which is why they are worth avoiding.

Submit keys#

Use onStandaloneInputAction to tell submit-style keys apart at the keyboard level, which is how a dialer or a search bar gets wired.

VirtualKeypad(
  standalone: true,
  onStandaloneInputAction: (action, text) {
    switch (action) {
      case KeyAction.next:
        FocusScope.of(context).nextFocus();
      case KeyAction.done:
      case KeyAction.search:
        submit(text);
      default:
        break;
    }
  },
)

The callback receives the action and the field's current text, so a search bar can submit the query without reaching for the controller. The key's label follows each field's textInputAction, so a field marked next shows a next key and the one after shows done.

Hiding it when nothing is focused#

VirtualKeypad(
  standalone: true,
  hideWhenUnfocused: true,
  animationDuration: const Duration(milliseconds: 180),
)

Useful on a phone-sized layout, where 280 pixels of keyboard under an unfocused form is wasted space. On a kiosk you usually want the opposite, so leave it off.

When to reach for scope mode instead#

Standalone mode is the right default. Move to VirtualKeypadScope with VirtualKeypadTextField when you want to own the text yourself: reading the cursor position, inserting programmatically, or driving something that is not a TextField at all.

final controller = VirtualKeypadController();

VirtualKeypadScope(
  child: Column(
    children: [
      VirtualKeypadTextField(controller: controller),
      const VirtualKeypad(),
    ],
  ),
)

controller.insertText('hello');
controller.deleteBackward();