Keyboard
A full on-screen keyboard: 100%, 75% and 65% boards, a number pad, and a phone layout. Every cap is a button that strikes, lights up in tone, and reports itself through onKeyPress. interactive={false} turns the board into a static diagram, and floating docks it over the page.
Basic
The default 100% board. Each press reports the cap's code - its KeyboardEvent.code - and, when the cap types a character, its value.
Press any cap.
- The whole board is a single tab stop; the arrow keys move between caps from there
- Caps size themselves from the container, so the board shrinks to fit a narrow column instead of overflowing it
Layouts
layout picks the board: full (default), tkl, compact, numpad, or phone.
compact - a 65% board: no function row, and the arrows tuck under the right Shift.
- On a phone-width page use phone or numpad - they are the only layouts whose caps stay above a 24x24px touch target there
- phone carries a single legend per cap, so Shift on it changes letter case only
Sizes
size sets the cap scale: sm, md (default), or lg. It is a ceiling - a board given less room shrinks below it.
Accent keys
No cap is coloured until accentKeys names it by code. tone sets that fill, and the colour a struck cap lights up in.
Diagramming a shortcut
interactive={false} renders plain <kbd> caps with no tab stop. Paired with accentKeys, the board shows where a binding sits.
Press ⌘ ⇧ P to open the command palette.
- Keep the shortcut in text as well - accent colour alone does not tell a screen reader which caps matter
Driving an input
onKeyPress is the whole integration: append a cap's value, and branch on code for the caps that type nothing.
- Shift and Caps Lock latch and light a lamp on the cap. Shift takes a cap's upper legend where it has one (1 types !) and otherwise flips case; Caps Lock only flips case
- A Shift armed by clicking applies to the next cap and then lets go, the way on-screen keyboards behave
- Arrows, Backspace, Enter, Home/End and the function row report a code and no value
- A held cap repeats like a real key. The preview edits at the caret: select text and type over it, or use ⌘A, ⌘C, ⌘X and ⌘V
Mirroring the real keyboard
The two keyboards work as one: a real key sends its cap down, travelling and clicking as a press on that cap would. A board listens only while it is engaged - open, if it floats, or holding focus if it sits in flow.
Click this board, then type - each cap goes down with its key. Other boards on the page stay quiet.
- On by default. captureKeys={false} opts out entirely
- Client-only: it listens on window after hydration and does nothing during server rendering
- A mirrored press never fires onKeyPress - the real key already reached whatever had focus, so reporting it again would type every character twice
- A key still held when the window loses focus is released, so a cap never stays lit after switching apps
- The physical Shift and the on-screen Shift are one modifier - hold Shift and click a cap to get its shifted value
Floating over the page
floating docks the board to the bottom of the viewport instead of laying it out in flow. Try its props here - the snippet follows your settings. Hold a cap to repeat it; click ⌘ then A, C or V for select all, copy and paste.
- Pressing a cap never takes focus from the field being typed into, and the margins either side of the board stay click-through
- Presses on triggerRef are ignored by outside-press dismissal - here it is the whole panel, so changing a setting keeps the board open
- onKeyPress receives the modifiers a press was made under; under ⌘ or Ctrl a cap reports no value, so a handler can act on ⌘A, ⌘C, ⌘X and ⌘V
- A held cap repeats after a 400ms pause; Shift, Caps Lock, ⌘, Ctrl, Alt, Fn and Esc never repeat
- width overrides the screen-relative width size picks (roughly 45vw, 60vw or 75vw)
- Rendered through FujiPortal here: an ancestor with a transform, filter or backdrop-filter - a glass surface, for one - otherwise becomes the box a fixed board docks to
Docked inside a parent
anchor='parent' positions the board against its nearest positioned ancestor instead of the viewport - a keypad that fills its own card. Give that ancestor position: relative.
- sound plays a short synthesised click on every press. It is off by default, stays silent until the first press, and the board draws its own toggle for it - defaultSound to let the board own that state, sound plus onSoundChange to own it yourself
- A number pad has no Backspace, so Enter clears the PIN in this demo
When to use
- An on-screen keypad or keyboard people press - a kiosk, a PIN pad, a touch surface with no hardware keyboard.
- Documenting a shortcut on a whole board, where the position of the keys matters (interactive={false} with accentKeys).
- Visualising live keyboard input - a typing tutor or a shortcut tester (captureKeys).
When not to use
- Naming a key or chord inline in prose or beside a menu item - use Kbd.
- Collecting a one-time code from people who already have a keyboard - use OTP Input with the system keyboard.
- Replacing a phone's own soft keyboard for ordinary text entry - it adapts to language, autocorrect and assistive settings this board does not.
Keyboard
Mobile behavior
- Caps size from the container, so a board never overflows it - it shrinks instead.
- In a 375px-wide container only numpad and phone keep caps above the 24x24px WCAG 2.5.8 target; full, tkl and compact render roughly 15-18px caps there.
- A floating board sizes against the screen, or against its parent with anchor='parent', and never grows wider than either.
Accessibility notes
- The board is a group named by label (default "On-screen keyboard"); give it a label that says what it is for.
- Glyph caps carry real names - the arrow reads as "Arrow up", ⌘ as "Command left" - and the blank space bar is named "Space".
- Shift and Caps Lock expose aria-pressed, so a latched modifier is announced as well as lit.
- interactive={false} renders <kbd> caps outside the tab order and keeps glyph names as visually hidden text.
- disabled disables every cap and sets aria-disabled on the group.
Common mistakes
- Keeping the default 100% layout on a phone-width page, where its caps fall well under a usable touch target.
- Treating value as always present - modifier and action caps report a code and no value.
- Letting accent colour alone explain a shortcut - name the keys in text too.
- Rendering a viewport-anchored floating board inside a transformed, filtered or glass ancestor without a portal.
Known limitations
- US QWERTY legends only - no locale or alternative layouts, and no symbol page on the phone layout.
- Fn has no KeyboardEvent.code in any browser, so captureKeys can never light it.
- A floating board is not modal: it traps no focus and leaves the rest of the page interactive.
onKeyPress payload (KeyboardKeyDef)
The cap's identity - a KeyboardEvent.code value ("KeyA", "Escape", "Numpad7"). accentKeys matches on it.
The legend printed on the cap.
Accessible name where the legend alone does not identify the cap ("Arrow up"). Falls back to label.
Upper legend - the shifted character ("!" over "1").
The character this press types, with Shift and Caps Lock applied. Absent on modifier and action caps.
Cap width in key units.
Cap height in key units - 2 for the number pad's + and Enter.
Drawn blank while label stays its accessible name (the space bar).
Modifier caps take the muted grouping tone.
API reference
`KeyboardEvent.code` values painted in `tone`, like a real board's coloured Esc and arrows. Empty by default: cap colour should mean something the consumer chose.
Box a floating board is positioned against. Default "viewport".
Mirrors the physical keyboard (strike, click, shared Shift/Caps Lock); on by default, ignored in SSR. Listens only while engaged (open, or focus inside) and never calls `onKeyPress` (it already typed).
Extra classes for the named regions.
Open state of a floating board, uncontrolled.
Initial sound state when the board owns it. Default `false`.
Dims the board and stops it reporting presses.
Close a floating board on Escape and on a pointer press outside. Default `true`.
Float the board over `anchor`, centred, unmounted while closed. The margins around it stay click-through, so a docked keyboard never blocks the page it types into.
Whether the caps are buttons. Default `true`. Set `false` for a static shortcut diagram of plain `<kbd>` elements with no tab stop.
Accessible name for the board. Default "On-screen keyboard".
Which board to draw. Default "full". Only "numpad" and "phone" clear the 24x24px touch-target floor at `size="md"` in a 375px container; the others need a wider container or larger `size`.
Called with the pressed cap and the modifiers it was pressed under; a held cap repeats. Under ⌘ or Ctrl the cap reports no `value` (a shortcut types nothing) - branch on `modifiers` for ⌘A, ⌘C, ⌘V.
Called with the board's next open state, from Escape, an outside press, or the trigger.
Called with the next sound state, from the board's own toggle.
Open state of a floating board, controlled. Ignored when not floating.
Where inside that box it sits. Default "bottom".
Cap scale. The board also shrinks below this to fit its container. Default "md".
Plays a short click on every press (controlled). Off by default. Synthesised via Web Audio, so no asset or dependency; silent until the first press, since browsers block gesture-less audio.
Shows the board's own sound toggle in a strip above the caps. Defaults to `interactive` (static diagrams make no sound). Set `false` where the app already offers the control.
Color a cap lights up in when it is struck, and the color `accentKeys` paints. Default "fire".
The control that opens the board. Excluded from outside-dismissal, so clicking it while open closes the board instead of closing and immediately reopening it.
Target board width (CSS length, px, or `%` of its box); never outgrows its box, lifts the size cap. Floating default ~45vw/60vw/75vw for `sm`/`md`/`lg` (max 96vw); unset in flow, where `size` applies.
Also accepts everything from Omit<React.HTMLAttributes<HTMLDivElement>, "onKeyPress">.
Coding agents read this reference, with its examples, from the version your project has installed, through the Fuji UI MCP server. Set up your agent
