polyboard
A multilingual, draggable on-screen keyboard for Flutter — desktop, touch and web. Built for kiosks, POS terminals and any touchscreen without a physical keyboard.
- ⌨️ Numeric pad, QWERTY/alphabetic and symbols.
- 🌍 English · हिन्दी · العربية (RTL) · Русский out of the box — and any non-CJK script via a few lines of layout data. Bundled Noto fonts so Devanagari/Arabic render everywhere.
- 🖱️ Drag the handle to dock the keyboard to the top or bottom; remembered per device.
- 🎯 Focus-safe — tapping a key never dismisses the field (the usual
onTapOutsidetrap is handled for you). - 🔌 Zero dependencies beyond Flutter. Pluggable storage, theme, logger and callbacks — works with Riverpod, Provider, or nothing.
- ✍️ Drives any
TextEditingController; auto right-aligns once RTL text is typed.
Chinese & Japanese need an IME (phonetic → candidate selection), not a key layout. polyboard ships the IME hook (
PolyboardImeEngine) so a conversion engine can be attached; the engine/dictionaries ship separately.
Install
dependencies:
polyboard: ^0.1.0
Quick start
import 'package:flutter/material.dart';
import 'package:polyboard/polyboard.dart';
final keyboard = PolyboardController();
void main() => runApp(MaterialApp(
builder: Polyboard.builder(controller: keyboard),
home: const Demo(),
));
class Demo extends StatelessWidget {
const Demo({super.key});
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: PolyboardTextField(
decoration: const InputDecoration(hintText: 'Type here'),
),
),
),
);
}
Any field works — PolyboardTextField just wires focus for you. To use your
own field, call Polyboard.of(context).attach(controller) on focus and
detach(controller) on blur.
Modes
PolyboardController(defaultMode: PolyboardMode.auto); // off | on | auto
- off — never shown.
- on — shown whenever a bound field is focused.
- auto — shown only when the field was focused by a touch tap (a touch terminal gets it; a mouse+keyboard operator doesn't).
Theme
PolyboardController(theme: PolyboardTheme.dark());
// or PolyboardTheme.light().copyWith(accentColor: Colors.teal)
Custom layouts & languages
A language is just data — any number of rows, an optional shift layer, and an
rtl flag:
const swedish = KbLayout(
code: 'sv',
label: 'SV',
rtl: false,
rows: [
['q','w','e','r','t','y','u','i','o','p','å'],
['a','s','d','f','g','h','j','k','l','ö','ä'],
['z','x','c','v','b','n','m'],
],
);
PolyboardController(layouts: const [kbEnglish, swedish]);
Callbacks, logging & Riverpod
No state-management dependency. Hook events directly:
PolyboardController(
logger: (m) => debugPrint('[polyboard] $m'),
onKey: (key) => switch (key) {
TextKey(:final text) => print('typed $text'),
ActionKey(:final action) => print('action $action'),
},
onVisibilityChanged: (v) {},
onLanguageChanged: (code) {},
);
Riverpod is a one-liner — the controller is a ChangeNotifier:
final keyboardProvider = Provider((ref) {
final c = PolyboardController();
ref.onDispose(c.dispose);
return c;
});
// MaterialApp(builder: Polyboard.builder(controller: ref.read(keyboardProvider)))
Persistence
By default preferences (mode/language/dock) last only for the session. Persist
them by passing a PolyboardStorage adapter (wrap shared_preferences, Hive,
etc.):
class PrefsStorage implements PolyboardStorage {
PrefsStorage(this.prefs);
final SharedPreferences prefs;
@override String? getString(String k) => prefs.getString(k);
@override void setString(String k, String v) => prefs.setString(k, v);
@override bool? getBool(String k) => prefs.getBool(k);
@override void setBool(String k, bool v) => prefs.setBool(k, v);
}
Roadmap
Long-press accents (unlocks Portuguese/Spanish/French/German), Thai (4-row Kedmanee), key-preview popups, caret-movement keys, emoji panel, resizable / free-float positioning, and the CJK IME engine.
License
MIT. Bundled Noto fonts are under the SIL Open Font License 1.1
(assets/fonts/OFL.txt).
Libraries
- polyboard
- Polyboard — a multilingual, draggable on-screen keyboard for Flutter desktop, touch and web.