VirtualKeyboard
ComponentsOn-screen inputOn-screen virtual keyboard for React — declarative layouts, headless hook, enterprise a11y
Live demo
Install
$ npm install @usefy/virtual-keyboardQuick start
import { useRef } from "react";
import { VirtualKeyboard, qwertyLayout } from "@usefy/virtual-keyboard";
function Search() {
const inputRef = useRef<HTMLInputElement>(null);
return (
<>
<input ref={inputRef} placeholder="Search…" />
<VirtualKeyboard
inputRef={inputRef}
layouts={qwertyLayout}
submitOnEnter
onEnter={(query) => runSearch(query)}
/>
</>
);
}API reference
<VirtualKeyboard /> props
Extends every useVirtualKeyboard option, plus:
| Prop | Type | Description |
|---|---|---|
variant | "inline" | "docked" | "floating" | Placement: inline (in flow, always visible — default); docked (fixed full-width bar at the bottom of the viewport); floating (elevated floating panel above the bottom edge). |
open | boolean | Controlled open state for docked/floating (ignored by inline). |
defaultOpen | boolean | Uncontrolled initial open state. Defaults to closed when a trigger is present, otherwise open. |
onOpenChange | (open: boolean) => void | Fired when the open state changes (trigger click, Escape, outside click). |
closeOnClickOutside | boolean | Close docked/floating on a pointer press outside the panel. Defaults to true when a trigger is present, otherwise false (a trigger-less panel would have no way to reopen). The trigger and a bound inputRef are always excluded; inline ignores it. |
trigger | ReactNode | The "keyboard icon" affordance. Wrapped in an accessible toggle button; content must be non-interactive. |
triggerLabel | string | Accessible name for an icon-only trigger (visible text is preferred when present). |
zIndex | number | Stacking for the fixed docked/floating variants (default 1000). |
enableVariants | boolean | Show a corner dot on keys with variants and open an accent chooser on long-press / right-click. |
enableKeyRepeat | boolean | Auto-repeat repeatable action keys while held. Only Backspace repeats today; char keys never do. |
randomize | boolean | Shuffle char-key positions once per mount (secure PIN pads). Action/modifier keys stay put; values are unchanged. Changing layouts after mount has no effect while on — remount (a changing key) to reshuffle. |
sound | boolean | Play a synthesized Web Audio click on each press (no asset, CSP-friendly). The AudioContext is created lazily on the first press; no-ops where unsupported. Rides taps/clicks only (a held auto-repeat doesn't click each tick). Built-in keys only — renderKey keys bypass it. |
haptics | boolean | Vibrate ~10ms on each press via navigator.vibrate where supported (independent of sound). Built-in keys only — renderKey keys bypass it. |
theme | "light" | "dark" | "system" | Color theme (default "system"). |
classNames | { root?, row?, key?, keyActive? } | Per-part class overrides. |
renderKey | (key, defaultProps) => ReactNode | Render keys yourself (advanced). Custom keys use the raw press path, so built-in sound/haptics and the variants/auto-repeat gestures don't apply — wire your own. |
className / style | — | Applied to the root element. |
Docked layout note: a
dockedkeyboard isposition: fixedat the bottom of the viewport, so it overlays page content. Reserve space for it (e.g. bottom padding on your scroll container equal to the keyboard height) so the bar never covers the input it types into.RTL: give a layout
direction: "rtl"and the keyboard renders mirrored (dir="rtl"on the root; rows flip via CSS, the data order is unchanged) and Arrow-key navigation mirrors with it (ArrowLeft → visually-left key). Home/End and Up/Down stay logical.
useVirtualKeyboard options
| Option | Type | Description |
|---|---|---|
layouts | KeyboardLayout | KeyboardLayout[] | Registered layouts (first is initial). Defaults to QWERTY. |
defaultLayout | string | Name of the initial layout. |
value / defaultValue / onChange | — | Controlled / uncontrolled value ownership. |
inputRef | RefObject<HTMLInputElement | HTMLTextAreaElement> | Ref-bound target. |
maxLength | number | Reject inserts beyond this length. |
keyFilter | (key, nextValue) => boolean | Reject an insertion. |
submitOnEnter | boolean | Enter fires onEnter instead of a newline. |
onKeyPress / onEnter / onLayoutChange | — | Callbacks. |
useVirtualKeyboard returns
value (committed text), composing (the pending IME block, empty without a
Composer), layout, layoutNames, modifiers, and the stable actions
press, insert, backspace, clear, setValue, setLayout, toggleShift,
toggleCapsLock, plus prop-getters getKeyboardProps, getRowProps,
getKeyProps.
Also exported: qwertyLayout, azertyLayout, qwertzLayout, dvorakLayout,
colemakLayout, numericLayout, phoneLayout, emailLayout, LATIN_VARIANTS
(the shared accent map), createLayout, resolveLayout, randomizeLayout,
identityComposer, and all types. randomizeLayout(layout, rng?) is a pure, seedable Fisher–Yates shuffle
of a layout's char-key positions — inject an RNG for a deterministic /
reproducible pad.
The opt-in @usefy/virtual-keyboard/hangul subpath adds hangulComposer and
hangulLayout (Korean 두벌식) — see IME Composition.
Go deeper
This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.