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 top/bottom, or switch to a free-floating panel you can move anywhere; resize with +/−. All remembered per device.
  • 😀 Emoji panel, key-preview popups, and caret-movement keys.
  • 🎯 Focus-safe — tapping a key never dismisses the field (the usual onTapOutside trap 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), suggestions strip, and the CJK IME engine (the hook is already in place via PolyboardImeEngine).

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.