Skip to content
Component

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.

sm
md
lg

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.

`1234567890-=BackspaceDelTabQWERTYUIOP[]\HomeCapsASDFGHJKL;'EnterPgUpShift leftZXCVBNM,./Shift rightArrow upPgDnCtrlOptionCommand leftSpaceCommand rightFnArrow leftArrow downArrow right
  • 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.

layout
tone
size
placement
  • 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

KeyAction
TabEnters and leaves the board as a single stop, landing on the last focused cap.
Arrow keysMove focus to the nearest cap in that direction by position, across rows of different lengths.
Home / EndMove focus to the first / last cap on the board.
Enter / SpacePress the focused cap: it strikes and reports through onKeyPress.
EscapeCloses an open floating board, unless dismissible is false.

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.

Related components

onKeyPress payload (KeyboardKeyDef)

codeNone
string

The cap's identity - a KeyboardEvent.code value ("KeyA", "Escape", "Numpad7"). accentKeys matches on it.

labelNone
string

The legend printed on the cap.

nameNone
string

Accessible name where the legend alone does not identify the cap ("Arrow up"). Falls back to label.

secondaryNone
string

Upper legend - the shifted character ("!" over "1").

valueNone
string

The character this press types, with Shift and Caps Lock applied. Absent on modifier and action caps.

width1
number

Cap width in key units.

height1
number

Cap height in key units - 2 for the number pad's + and Enter.

hideLabelNone
boolean

Drawn blank while label stays its accessible name (the space bar).

role"alpha"
"alpha" | "modifier"

Modifier caps take the muted grouping tone.

CriterionStatusNote
Keyboard supportSupportedOne tab stop with a roving tabindex. Arrow keys move geometrically between caps, Home/End jump to the ends, and Enter or Space presses the focused cap.
Focus managementSupportedA floating board never takes focus from the field it types into and closes on Escape. It is not modal, so there is no focus trap. Changing layout under a focused board keeps a cap in the tab order.
Screen reader labelsSupportedA named group; glyph caps are named (Arrow up, Command left), Shift and Caps Lock announce aria-pressed, and static caps keep their names as visually hidden text.
Reduced motionSupportedThe key strike and the floating board's entrance both run on Fuji UI duration tokens, which are 0ms under prefers-reduced-motion.
Reduced transparencySupportedAn in-flow board is opaque; a floating board's glass overlay surface flattens to opaque under prefers-reduced-transparency.
Mobile touch behaviorPartialOnly the numpad and phone layouts keep caps above 24x24px in a 375px-wide container; full, tkl and compact fall well below it there.

API reference

accentKeysEMPTY_KEYS
string[]

`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.

anchor"viewport"
KeyboardAnchor

Box a floating board is positioned against. Default "viewport".

captureKeystrue
boolean

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).

classNamesNone
Partial<Record<"key" | "root" | "deck", string>>

Extra classes for the named regions.

defaultOpenNone
boolean

Open state of a floating board, uncontrolled.

defaultSoundfalse
boolean

Initial sound state when the board owns it. Default `false`.

disabledfalse
boolean

Dims the board and stops it reporting presses.

dismissibletrue
boolean

Close a floating board on Escape and on a pointer press outside. Default `true`.

floatingfalse
boolean

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.

interactivetrue
boolean

Whether the caps are buttons. Default `true`. Set `false` for a static shortcut diagram of plain `<kbd>` elements with no tab stop.

label"On-screen keyboard"
string

Accessible name for the board. Default "On-screen keyboard".

layout"full"
KeyboardLayout

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`.

onKeyPressNone
((key: KeyboardKeyDef, modifiers: KeyboardModifiers) => void)

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.

onOpenChangeNone
((open: boolean) => void)

Called with the board's next open state, from Escape, an outside press, or the trigger.

onSoundChangeNone
((sound: boolean) => void)

Called with the next sound state, from the board's own toggle.

openNone
boolean

Open state of a floating board, controlled. Ignored when not floating.

placement"bottom"
KeyboardPlacement

Where inside that box it sits. Default "bottom".

size"md"
ComponentSize

Cap scale. The board also shrinks below this to fit its container. Default "md".

soundNone
boolean

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.

soundToggleinteractive
boolean

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.

tone"fire"
ComponentTone

Color a cap lights up in when it is struck, and the color `accentKeys` paints. Default "fire".

triggerRefNone
React.RefObject<HTMLElement | null>

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.

widthNone
string | number

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