Skip to content
usefy

VirtualKeyboard

ComponentsOn-screen input

On-screen virtual keyboard for React — declarative layouts, headless hook, enterprise a11y

Live demo

Install

$ npm install @usefy/virtual-keyboard

Quick start

VirtualKeyboard.tsx
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:

PropTypeDescription
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).
openbooleanControlled open state for docked/floating (ignored by inline).
defaultOpenbooleanUncontrolled initial open state. Defaults to closed when a trigger is present, otherwise open.
onOpenChange(open: boolean) => voidFired when the open state changes (trigger click, Escape, outside click).
closeOnClickOutsidebooleanClose 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.
triggerReactNodeThe "keyboard icon" affordance. Wrapped in an accessible toggle button; content must be non-interactive.
triggerLabelstringAccessible name for an icon-only trigger (visible text is preferred when present).
zIndexnumberStacking for the fixed docked/floating variants (default 1000).
enableVariantsbooleanShow a corner dot on keys with variants and open an accent chooser on long-press / right-click.
enableKeyRepeatbooleanAuto-repeat repeatable action keys while held. Only Backspace repeats today; char keys never do.
randomizebooleanShuffle 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.
soundbooleanPlay 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.
hapticsbooleanVibrate ~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) => ReactNodeRender 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 / styleApplied to the root element.

Docked layout note: a docked keyboard is position: fixed at 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

OptionTypeDescription
layoutsKeyboardLayout | KeyboardLayout[]Registered layouts (first is initial). Defaults to QWERTY.
defaultLayoutstringName of the initial layout.
value / defaultValue / onChangeControlled / uncontrolled value ownership.
inputRefRefObject<HTMLInputElement | HTMLTextAreaElement>Ref-bound target.
maxLengthnumberReject inserts beyond this length.
keyFilter(key, nextValue) => booleanReject an insertion.
submitOnEnterbooleanEnter fires onEnter instead of a newline.
onKeyPress / onEnter / onLayoutChangeCallbacks.

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.

More in components