An open API service indexing awesome lists of open source software.

https://github.com/ai/keyux

JS library to improve keyboard UI of web apps
https://github.com/ai/keyux

Last synced: about 1 year ago
JSON representation

JS library to improve keyboard UI of web apps

Awesome Lists containing this project

README

          

# KeyUX

JS library to improve the keyboard UI of web apps. It is designed not only
for **a11y**, but also to create **professions tools** where users prefer
to use the keyboard.

* Add **hotkeys** with `aria-keyshortcuts`.
* Show a button’s `:active` state when a hotkey is pressed.
* Enable **navigation with keyboard arrows** in `role="menu"` lists.
* Jump to the next section according to `aria-controls` and back
with Esc.
* Show and hide submenus of `role="menu"`.
* Allow users to **override hotkeys**.
* **2 KB** (minified and brotlied). No dependencies.
* Vanilla JS and works with any framework including React, Vue, Svelte.

```jsx
export const Button = ({ hotkey, children }) => {
return
{children}
{likelyWithKeyboard(window) && {getHotKeyHint(window, hotkey)}}

}
```

See [demo page](https://ai.github.io/keyux/)
and [example](./test/demo/index.tsx):

https://github.com/user-attachments/assets/bcd78271-cf76-45a3-8beb-4f3cea69c143

---

- [Install](#install)
- [Hotkeys](#hotkeys)
- [Hotkeys Hint](#hotkeys-hint)
- [Pressed State](#pressed-state)
- [Hotkeys Override](#hotkeys-override)
- [Hotkeys for List](#hotkeys-for-list)
- [Meta instead of Ctrl on Mac](#meta-instead-of-ctrl-on-mac)
- [Focus Groups](#focus-groups)
- [`focusgroup` attribute](#focusgroup-attribute)
- [Menu](#menu)
- [Listbox](#listbox)
- [Tablist](#tablist)
- [Toolbar](#toolbar)
- [Focus Jumps](#focus-jumps)
- [Nested Menu](#nested-menu)

---

Made at Evil Martians, product consulting for developer tools.

---

## Install

```sh
npm install keyux
```

Then add the `startKeyUX` call with the necessary features to the main JS file.

```js
import {
hiddenKeyUX,
hotkeyKeyUX,
hotkeyOverrides,
jumpKeyUX,
focusGroupKeyUX,
focusGroupPolyfill,
pressKeyUX,
startKeyUX
} from 'keyux'

const overrides = hotkeyOverrides({})

startKeyUX(window, [
hotkeyKeyUX([overrides]),
focusGroupKeyUX(),
focusGroupPolyfill(),
pressKeyUX('is-pressed'),
jumpKeyUX(),
hiddenKeyUX()
])
```

## Hotkeys

When the user presses a hotkey, KeyUX will click on the button or link
with the same hotkey in `aria-keyshortcuts`.

For instance, KeyUX will click on this button if user press
Alt+B or B.

```jsx
Bold
```

You can use hotkey to move focus to text input or textarea:

```jsx

```

The hotkey pattern should contain modifiers like `meta+ctrl+alt+shift+b`
in this exact order.

To enable this feature, call `hotkeyKeyUX`:

```js
import { hotkeyKeyUX, startKeyUX } from 'keyux'

startKeyUX(window, [
hotkeyKeyUX()
])
```

Hotkeys inside block with `inert` or `aria-hidden` attribute will be ignored.
You can use it, to disable page’s hotkeys when dialog is shown:

```html

Help

```

### Hotkeys Hint

You can render the hotkey hint from the `aria-keyshortcuts` pattern in
a prettier way:

```jsx
import { likelyWithKeyboard, getHotKeyHint } from 'keyux'

export const Button = ({ hokey, children }) => {
return
{children}
{likelyWithKeyboard(window) && {getHotKeyHint(window, hotkey)}}

}
```

`likelyWithKeyboard()` returns `false` on mobile devices where user is unlikely
to be able to use hotkeys (but it is still possible by connecting an
external keyboard).

`getHotKeyHint()` replaces modifiers for Mac and makes text prettier.
For instance, for `alt+b` it will return `Alt + B` on Windows/Linux or `⌥ B`
on Mac.

If you’re using overrides, pass the same override config both to `hotkeyKeyUX()`
and `getHotKeyHint()` for accurate hints:

```js
import {
getHotKeyHint,
hotkeyOverrides,
hotkeyKeyUX,
startKeyUX
} from 'keyux'

let config = { 'alt+b': 'b' }

startKeyUX(window, [
hotkeyKeyUX([hotkeyOverrides(config)]) // Override B to Alt + B
])
getHotKeyHint(window, 'b', [hotkeyOverrides(config)]) // Alt + B
```

One-letter hotkeys (like B) will be ignored if user’s focus is inside
text inputs or [focus groups](#focus-groups). This is why for general hotkeys
we recommend add some modifier like Alt+B.

### Pressed State

KeyUX can set class to show pressed state for a button when user
presses a hotkey. It will make the UI more responsive.

```js
import { hotkeyKeyUX, startKeyUX, pressKeyUX } from 'keyux'

startKeyUX(window, [
pressKeyUX('is-pressed'),
hotkeyKeyUX()
])
```

```css
button {
&:active,
&.is-pressed {
transform: scale(0.95);
}
}
```
overriding
You can use
[`postcss-pseudo-classes`](https://github.com/giuseppeg/postcss-pseudo-classes)
to automatically add class for every `:active` state in your CSS.

### Hotkeys Override

Many users want to override hotkeys because your hotkeys can conflict with
their browser’s extensions, system, or screen reader.

KeyUX allows overriding hotkeys using tranforms. Use the `hotkeyOverrides()`
tranformer with `hotkeyKeyUX()` and `getHotKeyHint()`.

You will need to create some UI for users to fill this object like:

```js
const overrides = {
'alt+b': 'b' // Override B to Alt + B
}
```

Then KeyUX will click on `aria-keyshortcuts="b"` when
Alt+B is pressed, and
`getHotKeyHint(window, 'b', [hotkeyOverrides(overrides)])` will return
`Alt + B`/`⌥ B`.

### Hotkeys for List

Websites may have hotkeys for each list element. For instance, for “Add to card”
button in shopping list.

To implement it:
1. Hide list item’s buttons by `data-keyux-ignore-hotkeys` from global search.
2. Make list item focusable by `tabindex="0"`. When item has a focus,
KeyUX ignores `data-keyux-ignore-hotkeys`.

```jsx


  • {product.title}
    Add to card

  • ```

    If you have common panel with actions for focused item, you can use
    `data-keyux-hotkeys` with ID of item’s panel.

    ```js


      {products.map(product => {
      return

    • {product.title}

    • })}


    Add to card

    ```

    ### Meta instead of Ctrl on Mac

    It’s common to use the Meta (or ) modifier for hotkeys
    on Mac, while Windows and Linux usually favor the Ctrl key. To provide
    familiar experience on all platforms, enable the Mac compatibility transform:

    ```js
    import {
    hotkeyMacCompat,
    hotkeyKeyUX,
    startKeyUX,
    getHotKeyHint
    } from 'keyux'

    const mac = hotkeyMacCompat();
    startKeyUX(window, [hotkeyKeyUX([mac])])
    getHotKeyHint(window, 'ctrl+b', [mac]) // Ctrl+B on Windows/Linux and ⌘+b on Mac
    ```

    Hotkeys pressed with the Meta modifier will work as if
    the Ctrl modifier was pressed.

    ## Focus Groups

    Using only Tab for navigation is not very useful. User may need to
    press it too many times to get to their button (also non-screen-reader users
    don’t have quick navigation).

    ### `focusgroup` attribute

    Key UX has limited polyfill for [`focusgroup` attribute](https://open-ui.org/components/focusgroup.explainer/) to mark groups where user will move `:focus`
    by arrows.

    ```html


    Copy
    Paste
    Cut

    ```

    Key UX supports (you can combine these features):
    - `focusgroup="block"` for vertical arrows.
    - `focusgroup="no-memory"` to not restore last focus position.
    - `focusgroup="wrap"` enables cyclic focus movement within a group.
    - `focusgroup="none"` excludes element from arrow key navigation.

    Key UX doesn’t support `grid` feature.

    To enable this feature, call `focusGroupPolyfill`.

    ```js
    import { focusGroupPolyfill } from 'keyux'

    startKeyUX(window, [
    focusGroupPolyfill()
    ])
    ```

    ### Menu

    To reduce Tab-list you can group website’s menu
    into [`role="menu"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menu_role)
    with arrow navigation.

    ```html

    Home
    Catalog
    Contacts

    ```

    Users will use Tab to get inside the menu, and will use either
    arrows or Home,
    End or an item name to navigate inside. User can search the menu item
    by typing the first characters of the item text.

    To enable this feature, call `focusGroupKeyUX`.

    ```js
    import { focusGroupKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX()
    ])
    ```

    ### Listbox

    The [`role="listbox"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role)
    is used for lists from which a user may select one or
    more items which are static and, unlike HTML `` elements,
    may contain images.

    ```html


    • Pizza

    • Sushi

    • Ramen


    ```

    Users will use Tab to get inside the listbox, and will use either
    arrows or Home,
    End or an item name to navigate inside.

    To enable this feature, call `focusGroupKeyUX`.

    ```js
    import { focusGroupKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX()
    ])
    ```

    ### Tablist

    The [`role="tablist"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tablist_role)
    identifies the element that serves as the container for a set of tabs.
    The tab content should be marked by `[role="tabpanel']`.

    ```html


    Home
    About
    Contact

    ```

    Users will use Tab to get inside the tablist, and will use either
    arrows or Home,
    End.

    To enable this feature, call `focusGroupKeyUX`.

    ```js
    import { focusGroupKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX()
    ])
    ```

    ### Toolbar

    The [`role="toolbar"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/toolbar_role)
    defines the containing element as a collection of commonly used function buttons
    or controls represented in a compact visual forms. Buttons inside the `toolbar`
    must have `type="button"` attribute because the default one is `submit`.

    ```html



    Copy
    Paste
    Cut





    ```

    Users will use Tab to get inside the tablist, and will use either
    arrows or Home,
    End.

    To enable this feature, call `focusGroupKeyUX`.

    ```js
    import { focusGroupKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX()
    ])
    ```

    ## Focus Jumps

    After finishing in one section, you can move user’s focus to the next step
    to save time. For example, you can move the cursor to the page after the user
    selects it from the menu. Or, you can move the focus to the item’s form after
    the user selects an item in the list.

    You can control where the focus moves next with `aria-controls`.

    ```jsx


    {products.map(({ id, name }) =>
    {name}
    )}




    ```

    On Esc the focus will jump back.

    You can add `aria-controls` to `` to make the focus jump
    on Enter.

    ```html

    ```

    To enable this feature, call `jumpKeyUX`.

    ```js
    import { focusGroupKeyUX, jumpKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX(),
    jumpKeyUX()
    ])
    ```

    ### Nested Menu

    You can make nested menus with KeyUX with `aria-controls`
    and `aria-hidden="true"`.

    ```html
    Edit


    Undo
    Find


    Find…
    Replace…

    ```

    You can make the nested menu visible by diabling `hidden`, but you will
    have to set `tabindex="-1"` manually.

    To enable this feature, call `hiddenKeyUX`.

    ```js
    import { focusGroupKeyUX, jumpKeyUX, hiddenKeyUX } from 'keyux'

    startKeyUX(window, [
    focusGroupKeyUX(),
    jumpKeyUX(),
    hiddenKeyUX()
    ])
    ```