GrKbd

  • core
  • data
  • API · 6 props

Displays a keyboard key or shortcut in a `<kbd>` element.

import { GrKbd } from '@feugene/granularity/components/GrKbd'

Overview

When to take it

  • a key combination is shown — in a hint, in the help, in a menu item;
  • the combination depends on the platform — platform prints ⌘ on macOS and Ctrl on the rest;
  • there are several keys — keys assembles the combination with a separator, with no markup by hand;
  • native semantics is needed — it renders as a <kbd> tag rather than as a styled <span>.

When to take something else

NeedTake
A status or a tag is shownGrBadge
A hint about the purpose is neededGrTooltip
A fragment of code is shownthe markup of the application: there are no code blocks in the package
The combination opens the command paletteGrCommandPalette

Variants every tone and view, rendered by the real component

variant

  • split
  • merged
  • sequence

Sizes

  • xs
  • sm
  • md
  • lg

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • A keyboard shortcuts sheet

    Each shortcut is written once, the way it is pronounced — mod+K — and renders as ⌘K on a Mac and Ctrl+K elsewhere. merged writes a combination the way systems do, sequence says «G then I» for a chord, and split draws the arrows as separate keys pressed one after another.

    Anywhere
    Search or run a commandCtrlK
    Create an issueC
    Show this sheetShift/
    Go to
    InboxGI
    My issuesGM
    SettingsGS
    In a list
    Previous / next issueArrow UpArrow Down
    Open the issueEnter
    Assign to meCtrlShiftM
  • A menu that teaches its shortcuts

    Each item ends with its combination in the small xs size of a hint, not as a second label; the hint inside a sentence below uses the same component. The buttons also declare aria-keyshortcuts, because a picture of keys is not an announcement.

    Press Escape to close the menu.
  • A support article for two systems

    An article about a specific system names the platform on purpose: platform="apple" keeps ⌥⌘J in the macOS column even on a Windows laptop, and platform="other" keeps Ctrl+Shift+J in the other. Everywhere else the automatic choice is the honest one.

    Send us the browser consoleSupport article · 2 minutes
    macOS1. Open the console: OptionCommandJ2. Select all messages: CommandA3. Copy them for the ticket: CommandC
    Windows and Linux1. Open the console: CtrlShiftJ2. Select all messages: CtrlA3. Copy them for the ticket: CtrlC

API generated from the package · cannot drift

Props

PropTypedefaultDescription
keysstring | string[] | undefinedundefinedThe combination: as a string (`"mod+shift+K"`) or as a set of tokens (`['mod', 'K']`). The `mod` token is Cmd on macOS, Ctrl on the rest. A space in the string splits it into steps: `"g i"` is drawn as "G then I", and `"mod+k p"` as "⌘K then P". The array form stays a flat chord: the array does not tell a step from a key.
variant"split" | "merged" | "sequence" | undefined"merged"`merged` (the default) — the combination as a single plate, the way the systems themselves write it: `⌘K` on macOS, `Ctrl+K` on the rest. `split` — a plate per key. `sequence` — the chord "G then I": the keys are pressed one after another.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe height of the plate and the type size — to match the neighbouring text or button. Unset — from `GrConfigProvider`, otherwise `md`.
separatorstring | undefinedundefinedThe separator between the keys. Unset — auto: in a merged plate the symbols are glued (`⌘K`) and the words are separated with a plus (`Ctrl+K`); in `split` it is a plus, in `sequence` a word from the locale. An empty string leaves only a gap.
hotkeystring | undefinedundefinedThe identifier of an entry in the registry (`granularityHotkeysPlugin`). The combination is taken from there, so the hint cannot diverge from the binding. Stronger than `keys`: if that one is set too, the registry wins — otherwise two sources would argue silently.
platformGrKbdPlatform | undefined"auto"How to draw `mod` and the modifiers: `apple` — `⌘`, `other` — `Ctrl`. `auto` by default: the platform is detected after mounting, and the server renders the non-Apple look.

Slots

SlotTypeDescription
defaultanyA key or a combination instead of the `keys` prop.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrKbdProps, GrKbdSize, GrKbdPlatform } from '@feugene/granularity/components/GrKbd'

Filedist/types/components/GrKbd/GrKbd.vue.d.ts

GrKbdPropsinterface
interface GrKbdProps {
    size?: GrKbdSize;
    variant?: GrKbdVariant;
    keys?: string | string[];
    hotkey?: string;
    separator?: string;
    platform?: GrKbdPlatform;
}
GrKbdSizetype
type GrKbdSize = GrComponentSize;
GrKbdPlatformtype
type GrKbdPlatform = 'auto' | 'apple' | 'other';

Filedist/types/components/GrKbd/config.d.ts

grKbdConfigconst
const grKbdConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrKbd">;

Filedist/types/components/GrKbd/defaults.d.ts

GrKbdConfigurablePropsinterface
interface GrKbdConfigurableProps {
    size: GrKbdSize;
}

Filedist/types/components/GrKbd/safelist.d.ts

grKbdSafelistconst
const grKbdSafelist: string[];

Filedist/types/components/shared/hotkeyTokens.d.ts

GrKbdKeyNametype
type GrKbdKeyName = 'command' | 'option' | 'shift' | 'control' | 'enter' | 'escape' | 'tab' | 'backspace' | 'delete' | 'pageUp' | 'pageDown' | 'arrowUp' | 'arrowDown' | 'arrowLeft' | 'arrowRight';
GrKbdTokenGrouptype
type GrKbdTokenGroup = 'modifier' | 'editing' | 'navigation';
GrKbdTokenSpecinterface
interface GrKbdTokenSpec {
    token: string;
    aliases: readonly string[];
    group: GrKbdTokenGroup;
    apple: HotkeyKeyView;
    other: HotkeyKeyView;
}
GR_KBD_TOKENSconst
const GR_KBD_TOKENS: readonly GrKbdTokenSpec[];
findKbdTokenfunction
function findKbdToken(token: string): GrKbdTokenSpec | undefined;

Usage from the library prose

A single key and a combination
vue
<GrKbd>
Esc
</GrKbd>

<GrKbd keys="mod+K" />

<GrKbd :keys="['mod', 'shift', 'P']" />

The slot remains usable for a single key. The keys prop accepts both a string ("mod+shift+K") and an array of tokens — assembling ⌘ + K from two GrKbd and a <span>+</span> is no longer necessary.

A combination is marked up with nested <kbd> — a device from the HTML specification: that way it stays keyboard input as a whole rather than a set of neighbouring elements. Only which of them carries the border depends on the variant.

The variants
variantThe lookWhen
merged (the default)a single plate: ⌘K, Ctrl+Ka hint next to a button or in a field — that is how the systems themselves write a combination
splita plate per key: ⌘ Kwhen it is exactly the set of keys that has to be shown, in a keyboard-shortcuts reference for instance
sequenceG then Ia chord: the keys are pressed one after another rather than together
vue
<GrKbd keys="mod+K" />                      <!-- ⌘K -->

<GrKbd keys="mod+K" variant="split" />      <!-- ⌘ K -->

<GrKbd :keys="['G', 'I']" variant="sequence" />

The separator is computed by itself

separator is not set by default — that is auto:

  • in merged the separator is put only after a word: ⌘K and ⌘⇧K on macOS, Ctrl+K and Ctrl+Shift+K on the rest. Within one platform the set is uniform (macOS gives symbols, the rest give words), so the rule “a symbol on the left — glue them” means “write it the way the system does”;
  • in split it is +, in sequence a word from the locale (gr.kbd.then).

An explicit separator is stronger than the auto one: separator="" will glue anything, separator="+" will put a plus even between symbols. The separator is decorative (aria-hidden).

A sequence is written in the string itself

A space divides keys into steps, a plus — the keys within a step:

vue
<GrKbd keys="g i" />        <!-- G then I -->

<GrKbd keys="mod+k p" />    <!-- ⌘K then P -->

That way the hint and the binding are described with one string: the same 'g i' is understood by v-hotkey (keyboard.md). Before that the string 'g+i' meant a chord for the directive and a chain for the hint, and what told them apart was variant, which the directive does not see.

The boundary of a step is stronger than variant: “then” appears between steps regardless of it. variant="sequence" remains working for a string without spaces — the old notation keys="g+i" variant="sequence" gives the previous result.

The array form of keys remains a flat chord: which is a step and which is a key, an array does not distinguish.

A combination from the registry

hotkey takes a combination by id from the registry of the application (granularityHotkeysPlugin) — then the hint cannot diverge from the binding:

vue
<GrKbd hotkey="search.open" />

The prop is stronger than keys: when both are set, the registry wins, otherwise two sources would argue silently. An unknown id in a dev build gives a warning rather than an empty hint.

The dictionary of keys

Tokens set more than the modifiers:

Token (and aliases)macOSThe rest
mod⌘Ctrl
ctrl, alt, shift⌃, ⌥, ⇧Ctrl, Alt, Shift
enter / return↩Enter
esc, space, home, endas a wordas a word
tab⇥Tab
backspace⌫Backspace
delete / del⌦Del
pageup / pgup, pagedown / pgdn⇞, ⇟PgUp, PgDn
up, down, left, right↑ ↓ ← →↑ ↓ ← →

The case does not matter, and a glyph is accepted as a token too (↑, ⌘). Writing an arrow as a literal in the markup is neither necessary nor advisable: a token brings a readable name with it, while a bare glyph is pronounced by a screen reader as a sign.

The catalogue is not a table in the docs but data: GR_KBD_TOKENS is exported by the package, the showcase page is built from it, and it feeds the formatter as well. A copy of the list would diverge from the behaviour at the very first new key.

ts
import { GR_KBD_TOKENS, findKbdToken } from '@feugene/granularity'

const navigation = GR_KBD_TOKENS.filter(spec => spec.group === 'navigation')
const command = findKbdToken('⌘') // the same token as `meta`
The platform

The mod token is Cmd on macOS and Ctrl on the other platforms; ctrl, alt and shift are shown as symbols on Apple too (⌃, ⌥, ⇧) and as words on the rest.

vue
<GrKbd keys="mod+K" />                    <!-- detected automatically -->

<GrKbd keys="mod+K" platform="apple" />   <!-- always ⌘ -->

<GrKbd keys="mod+K" platform="other" />   <!-- always Ctrl -->

platform="auto" refines the platform after mounting: navigator in the first render would diverge from the server HTML and break the hydration. The non-Apple variant therefore always comes from the server, and the ⌘ appears on the client. If the audience is known in advance, platform removes even that repaint.

The same mod token is understood by the v-hotkey directive, so the hint and the binding are written the same way and do not diverge:

vue
<div v-hotkey="{ 'mod+K': openSearch }">
  <GrKbd keys="mod+K" />
</div>

The parsing and the normalisation live in components/shared/hotkey.ts — the same module GrCommandPalette uses to match its own opening combination. A shared module rather than an import from the directory of the palette: otherwise a GrKbd → GrCommandPalette edge would appear in the build, and granular doctor would count it as an undeclared dependency.

The screen reader

Symbol keys get a readable name beside the symbol:

html
<kbd><span aria-hidden="true">⌘</span><span class="sr-only">Command</span></kbd>

Without that a screen reader pronounces ⌘ as “place of interest sign”. Words (Ctrl, Shift, Enter) are read on their own — no name is added to them, otherwise it would come out as “Ctrl Control”. The texts of the names are the gr.kbd.* keys.

The sizes

The full scale of the package: xs, sm, md (the default), lg — the height, the minimum width, the padding, the type size and the gap between the keys change. size is read from GrConfigProvider.

min-w keeps a single symbol square: without it “K” would be narrower than “Esc”, and a row of shortcuts would jump.