npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

v-k-b

v0.0.3

Published

Virtual keyboard for React front-ends.

Readme

v-k-b ⌨️

Virtual keyboard library for React front-ends.

  • No styles or opinionated button components included - bring your design system components and custom styling.
  • Handle your own text field state with key press callbacks, including backspace and clear functions.
  • Minimum possible dependencies: react and react-dom as peer dependencies.

Install

npm i v-k-b

Use

Import the Keyboard component and Key type:

import { Keyboard, type Key } from "v-k-b";

In your application code, keep track of the text state:

function App() {
  const [text, setText] = useState("");

  function handleKeyPress(key: Key) {
    if (typeof key === "string") return setText(text + key);
    if (key.v) return setText(text + key.v);
    return setText(text + key.k);
  }

  function handleBackspace() {
    setText(text.substring(0, text.length - 1));
  }

  function handleClear() {
    setText("");
  }

  return (
    <>
      <Keyboard
        id="qwerty"
        rows={[
          [
            { k: "`", uK: "~" },
            { k: "1", uK: "!" },
            { k: "2", uK: "@" },
            { k: "3", uK: "#" },
            { k: "4", uK: "$" },
            { k: "5", uK: "%" },
            { k: "6", uK: "^" },
            { k: "7", uK: "&" },
            { k: "8", uK: "*" },
            { k: "9", uK: "(" },
            { k: "0", uK: ")" },
            { k: "-", uK: "_" },
            { k: "=", uK: "+" },
            { k: "Backspace ⌫", uK: "Backspace ⌫", cb: handleBackspace },
            { k: "Clear ⌧", uK: "Clear ⌧", cb: handleClear },
          ],
          [
            "q",
            "w",
            "e",
            "r",
            "t",
            "y",
            "u",
            "i",
            "o",
            "p",
            { k: "[", uK: "{" },
            { k: "]", uK: "}" },
            { k: "\\", uK: "|" },
          ],
          [
            { k: "Caps Lock", uK: "Caps Lock", special: "caps" },
            "a",
            "s",
            "d",
            "f",
            "g",
            "h",
            "j",
            "k",
            "l",
            { k: ";", uK: ":" },
            { k: "'", uK: '"' },
          ],
          [
            { k: "⇧ Shift/Caps", uK: "⇧ Shift/Caps", special: "shift-or-caps" },
            "z",
            "x",
            "c",
            "v",
            "b",
            "n",
            "m",
            { k: ",", uK: "<" },
            { k: ".", uK: ">" },
            { k: "/", uK: "?" },
            { k: "⇧ Shift", uK: "⇧ Shift", special: "shift" },
          ],
          [{ k: "Space", uK: "Space", v: " ", uV: " " }],
        ]}
        ButtonComponent={"button"}
        buttonActionProp={"onClick"}
        handlePress={(key) => handleKeyPress(key)}
        shiftOrCapsDoublePressMilliseconds={200}
      />

      <div>
        <p>
          <span>Input:</span> {text}
        </p>
      </div>
    </>
  );
}

How React button components work

Keyboard lets you pass your own React button components to use with the keyboard. The ButtonComponent prop accepts things like:

  • a Button imported from a library like React Aria Components
  • the string "button" to indicate you want to use the browser-native button
  • ...or whatever button-like thing you want to pass

Use the buttonActionProp prop to pass the name of the click handler for the button. For example, use the string "onClick" for a native <button> or "onPress" for a React Aria Components button.

(Part of the reason for creating this library was being able to use my organization's existing design system button components in a keyboard layout rather than a generic <button> or one from a random library with a different API.)

Use the buttonProps prop for passing common props to all buttons. For example, pass a custom class name that's used for styling buttons, a data-* attribute, etc.

Use the getButtonProps callback that receives information about the key being pressed to let you customize the rendering of individual buttons.

How rows of keys work

Keyboard accepts a rows array prop, which is an array of Key arrays.

How keys work

A Key can be a regular string KeyString:

// The "a" key.
const aKeyString: KeyString = "a";

A Key can also be a KeyObject. These have fields for:

  • Strings for lowercase and uppercase text displays and values (in case the text on the button is different that what gets produced by clicking the button):
    • k: Lowercase text display for the key
    • v: Lowercase value for the key
    • uK: Uppercase text display for the key
    • uV: Uppercase value for the key
  • A callback if you need special handling: cb
  • Handling is included for special keys:
    • "shift": To create a one-shot shift key (press once to enter uppercase mode, uppercase mode exits after the next key is pressed)
    • "caps": To create a caps lock key (press once to enter uppercase mode, press again to exit uppercase mode)
    • "shift-or-caps": To create a mobile phone-style shift key that can be double-pressed for caps lock

A KeyObject for a regular key like "a" might look like:

// The "a" key.
const aKeyObject: KeyObject = {
  // The `k` text display and `v` value
  // are the same for a key like this.
  k: "a",
  v: "a",

  // The `uK` uppercase text display and `uV` uppercase
  // value are the same for a key like this.
  uK: "A",
  uV: "A",
};

A KeyObject for a special key like backspace might look like:

const backspaceKeyObject: KeyObject = {
  k: "Backspace ⌫",
  uK: "Backspace ⌫",

  // This is a custom function you
  // write and pass to the `KeyObject`.
  cb: handleBackspace,
};

How capitalization (shift and caps lock) works

Keyboards start in lowercase mode and move between three explicit uppercase states:

  • lowercase
  • one-shot shift (isShifted)
  • caps lock (isCapsLocked)

The Keyboard component manages uppercase state internally by default, and the same logic is also exported through useKeyboard() when you want to share uppercase state with other UI.

You can access uppercase mode by including a Shift or Caps Lock key:

// Shift key
{
  k: "⇧ Shift",
  uK: "⇧ Shift",
  special: "shift"
},
// Capslock key
{
  k: "Caps Lock",
  uK: "Caps Lock",
  special: "caps"
},

You can also use a mobile phone-style "shift or caps lock" key with a double-press:

// Double-press the shift key to enter caps lock mode
{
  k: "⇧ Shift/Caps",
  uK: "⇧ Shift/Caps",
  special: "shift-or-caps"
},

You can specify the maximum amount of time a shift-or-caps button should work to enter caps lock mode by using the shiftOrCapsDoublePressMilliseconds prop, which defaults to 300 milliseconds if not specified.

Pressing any uppercase key while uppercase is active returns the keyboard to lowercase mode, except for a rapid second shift-or-caps press, which promotes one-shot shift into caps lock.

If you want to show the current uppercase state elsewhere in your UI, share a controller created with useKeyboard():

import { Keyboard, useKeyboard, type Key } from "v-k-b";
import { useState } from "react";

function App() {
  const [text, setText] = useState("");
  const keyboard = useKeyboard({
    handlePress: (key: Key) => {
      if (typeof key === "string") return setText((prev) => prev + key);
      return setText((prev) => prev + (key.v ?? key.k));
    },
  });

  return (
    <>
      <div aria-live="polite">
        {keyboard.isCapsLocked
          ? "Caps Lock On"
          : keyboard.isShifted
            ? "Shift On"
            : "Lowercase"}
      </div>

      <Keyboard
        id="shared-state"
        rows={[
          [
            { k: "⇧ Shift/Caps", uK: "⇧ Shift/Caps", special: "shift-or-caps" },
            { k: "Caps Lock", uK: "Caps Lock", special: "caps" },
            "a",
            "b",
          ],
        ]}
        ButtonComponent="button"
        buttonActionProp="onClick"
        keyboardController={keyboard}
      />

      <output>{text}</output>
    </>
  );
}

If you use a Key that's just a string, the Keyboard will infer how capitalization should work by using String.prototype.toLocaleUppercase().

If a KeyObject with a callback cb is pressed while in shift mode, the keyboard returns to lowercase mode.

How styling works

Any extra props passed to Keyboard are spread on the parent <div> being returned. Use this to target your styles by passing className, or use a CSS-in-JS library to style the component.

Accessibility

For labelling, the ariaLabel prop can be used to assign an aria-label directly. The ariaLabelledBy prop can be used to point to a label outside of the component using aria-labelledby.

The ariaRole prop defaults to "region" and can be overwritten as you wish.

Pass other accessibility props as needed and they will be spread to the parent <div>.

Development

Each supported front-end framework gets its own directory in src. The barrel files in dist are written by hand and will export all the individual components and common types. The dist folder gets checked into the repo with these barrel files, and they are not touched by the clean script for this reason.

License

See LICENSE.

Support

If this library is useful to you, please consider giving to Canadians for Justice and Peace in the Middle East.

Free Palestine from the river to the sea. 🇵🇸