Shortcuts
Put x-wk-shortcut on the element a shortcut acts on, a button, a link or a field, and the shortcut
does what a click on it does. Place <x-wirekit:: once in your layout: ? opens an
overview of the shortcuts that apply at that moment, with a switch that turns off the shortcuts made
of a single key. <x-wirekit:: opens the same overview from a button, for a
device without a keyboard.
Usage
A shortcut clicks its element, so it does exactly what the pointer does: a wire:click runs, a link
with wire:navigate navigates as Livewire does, a disabled button does nothing. .focus moves the
focus to the element instead, which is what a search field wants. A key held down fires its shortcut
once, as a held mouse button clicks once.
Writing a Shortcut
Keys of one press are joined by +, with the key last; the presses of a sequence are separated by a
space.
| Written | Means |
|---|---|
cmd+s |
⌘ S on a Mac, Ctrl+S elsewhere: cmd is Cmd or Ctrl, whichever the reader holds |
ctrl+s |
Ctrl+S on every platform, Control on a Mac |
alt+n |
Alt+N, or Option+N on a Mac |
shift+? |
Shift and the key that types ? |
/ |
The key that types / |
g o |
G, then O within a second |
cmd+enter |
⌘ ↩ on a Mac, Ctrl+Enter elsewhere |
option is another name for alt, and meta for cmd. A key that types no character has a name:
enter, esc, space, tab, backspace, delete, up, down, left, right, home, end,
pageup, pagedown and f1 to f24. Any other name is compared with the browser's
KeyboardEvent.
as written, MediaPlayPause for example.
Which Key Press Counts
A key is recognized by the character it types, so ? is the key that types ? on any layout: Shift+/
on a US keyboard, Shift+ß on a German one. With Alt or Shift in the shortcut a letter or a digit is
also recognized by its place on the keyboard, because Option+N types ˜ on a Mac.
A shortcut without a modifier needs none of Cmd, Ctrl and Alt to be held: s is not ⌘ S. Shift does
not turn a letter into its shortcut either, so Shift+G is not g.
Areas
A shortcut belongs to an area, and an open dialog decides which areas count:
| Area | When it applies |
|---|---|
| The page | No modal dialog is open |
| A dialog | The element sits in that dialog, and it is the topmost open one |
| Everywhere | Always: add .global |
A Modal or a Drawer therefore hides the page's shortcuts
while it is open, and its own apply. A search on ⌘ K that should open from inside a dialog as well
takes .global:
<x-wirekit::button x-wk-shortcut.global="cmd+k" x-on:click="$dispatch('wirekit-command-palette-show')">Search</x-wirekit::button>
Sequences
g o waits up to one second for the O after the G. A press that continues no sequence ends the
wait, and is then judged on its own. A G that starts no sequence is just a key nobody bound: it
reaches the page as it would without the shortcut.
What Goes First
A key press belongs to whoever takes it first, in this order:
- A control that took the key. A menu's type-ahead, a select-only combobox or a tree takes a letter for itself, and the shortcut does not fire.
- A field the reader types in. A shortcut without Cmd, Ctrl or Alt never fires while the focus is in a text field, a text area or an editor: the character stays a character. Shortcuts with Cmd, Ctrl or Alt work there too, so ⌘ S saves from inside a field.
- The switch for single keys, below.
- The area, above.
A key that the IME of a Chinese, Japanese or Korean keyboard is composing is never a shortcut.
Single Keys
A shortcut without Cmd, Ctrl or Alt, such as ?, / or g o, is a single-key shortcut, and the
reader can switch those off in the overview. A person who controls the computer by voice says words,
and each word arrives as keys: without the switch, dictating a sentence fires every shortcut whose
letters it contains. That is why WCAG 2.1.4
requires a way to turn them off.
The switch keeps its position in the browser. To keep it with the person instead, pass the stored
choice to the overview and store each change, which the overview announces as
wirekit:shortcuts-single-keys-changed on window:
# 1. A column for the choice: null until the person flips the switch, so the browser's memory counts until then
php artisan make:migration add_single_key_shortcuts_to_users_table --table=users
// database/migrations/xxxx_xx_xx_xxxxxx_add_single_key_shortcuts_to_users_table.php
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
// 2. true, false, or null for "not chosen yet"
$table->boolean('single_key_shortcuts')->nullable();
});
}
// routes/web.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
// 3. Store the switch with the signed-in person
Route::put('/settings/single-key-shortcuts', function (Request $request) {
$request->user()->forceFill(['single_key_shortcuts' => $request->boolean('enabled')])->save();
return response()->noContent();
})->middleware('auth');
// resources/js/app.js
// 4. The overview announces each flip of its switch on the window
window.addEventListener('wirekit:shortcuts-single-keys-changed', (event) => {
// 5. Laravel checks the CSRF token of a PUT: add <meta name="csrf-token" content="{{ csrf_token() }}"> to your layout's <head>
fetch('/settings/single-key-shortcuts', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ enabled: event.detail.enabled }),
});
});
{{-- resources/views/components/layouts/app.blade.php --}}
{{-- 6. The stored choice; null for a guest or a person who never chose, and then the browser's memory counts --}}
<x-wirekit::shortcuts :single-keys="auth()->user()?->single_key_shortcuts" />
A Screen That Turns Them Off
A barcode scanner types each code as a fast run of keys, and on a screen with a scanner every one of
them could fire a shortcut. Put data-wk-single-keys="off" on any element of such a screen: while it
is in the page, single-key shortcuts do not fire, and the overview says so instead of showing the
switch. Shortcuts with Cmd, Ctrl or Alt still work.
<section data-wk-single-keys="off">
<x-wirekit::input label="Scan a parcel" name="code" autofocus />
</section>
The Overview
<x-wirekit:: renders a Modal that ? opens and closes. It lists the
shortcuts that apply at the moment it opens, grouped as Everywhere, In this dialog and On this page,
each with its keys as the reader's platform names them. A shortcut that does not apply, because a
dialog hides it, its element is hidden or disabled, or single keys are off, is not listed.
Each shortcut is listed under its element's accessible name: the text of a button or a link, the
label of a field, or its aria-label. Where that name is not the right one for a list of shortcuts,
data-wk-shortcut-label gives another:
<x-wirekit::button size="sm" surface="outline" icon-only x-wk-shortcut="n" data-wk-shortcut-label="New order">
<x-slot:iconLeft><x-wirekit::icon name="plus" size="sm" aria-hidden="true" /></x-slot:iconLeft>
New
</x-wirekit::button>
? is a single-key shortcut itself, so it obeys the switch. With single keys off, the overview opens
from a button.
Opening It from a Button
<x-wirekit:: is a Button that opens the overview, labeled
"Keyboard shortcuts" in the reader's language unless you give it a slot. It is the way in on a
tablet or a phone, where ? cannot be typed. Anything else can open it by dispatching
wirekit:shortcuts-open on window:
<x-wirekit::shortcuts.trigger intent="neutral" surface="ghost" size="sm">Shortcuts</x-wirekit::shortcuts.trigger>
<x-wirekit::dropdown.item x-on:click="$dispatch('wirekit:shortcuts-open')">Keyboard shortcuts</x-wirekit::dropdown.item>
A Shortcut Bound Twice
Two elements with the same shortcut in the same area cannot both fire: the first one bound wins. A
single key that also starts a sequence in that area, g beside g o, means the sequence never
fires. With APP_DEBUG on, the overview's page warns about both in the browser console, naming the
two elements.
The Directive
| Directive | Does |
|---|---|
x-wk-shortcut="cmd+s" |
Clicks the element when the shortcut is pressed in its area |
x-wk-shortcut.focus="/" |
Focuses the element instead |
x-wk-shortcut.global="cmd+k" |
Applies everywhere, with a dialog open as well. Combines with .focus |
The value is read as written, not evaluated. The element gets aria-keyshortcuts with the shortcut
in the form assistive technology reads, unless it has one already.
| Attribute | On | Does |
|---|---|---|
data-wk-shortcut-label |
the element with x-wk-shortcut |
Its name in the overview |
data-wk-single-keys="off" |
any element | Turns single-key shortcuts off while it is in the page |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
single-keys |
bool|null |
null |
The stored choice for single-key shortcuts. Null uses the choice this browser remembers |
heading |
string|null |
null |
The overview's heading. Without it: "Keyboard shortcuts", in the reader's language |
level |
int |
2 |
The level of the area headings in the overview, from 1 to 6. Another value keeps 2 |
scope |
string|null |
null |
Scoped personalization key |
<x-wirekit::shortcuts.trigger> props
| Prop | Type | Default | Description |
|---|---|---|---|
intent |
string|null |
null |
The button's intent, as on Button |
surface |
string|null |
null |
The button's surface |
size |
string|null |
null |
The button's size |
scope |
string|null |
null |
Scoped personalization key |
Events
| Event | Direction | Detail |
|---|---|---|
wirekit:shortcuts-open |
you dispatch it on window, and the trigger does |
none. Opens the overview |
wirekit:shortcuts-single-keys-changed |
the overview dispatches it on window when the reader flips the switch |
enabled: whether single-key shortcuts now work |
Accessibility
- Every element with a shortcut of one press carries
aria-keyshortcuts,Control+S Meta+Sforcmd+s, so a screen reader can tell the reader the shortcut. A sequence has no form in that attribute, so it is named in the overview only. - Single-key shortcuts can be switched off (WCAG 2.1.4), and they never fire while the reader types in a field.
- A shortcut does what a click does, so a reader who uses it reaches the same place and hears the same result as one who clicks.
- The overview is a modal dialog with its heading as its name: the focus moves into it, stays there while it is open, and returns to where it was when it closes.
- The keys are drawn as
kbdelements. On a Mac they are the symbols the system's menus use, ⌘ ⌥ ⇧ ⌃, which VoiceOver reads by their names; elsewhere they are written out.
Keyboard Interaction
| Key | Action |
|---|---|
? |
Opens the overview, and closes it while it is open |
Escape |
Closes the overview |
Tab |
Moves through the switch and the close button inside the overview |
Space |
Flips the switch for single keys |
Pitfalls
- A shortcut on an element that is not rendered does nothing. An element hidden by
display: none, inside aninertregion or disabled neither acts nor is listed. - Browsers keep a few shortcuts for themselves. ⌘ W, ⌘ T and ⌘ N close or open windows and tabs before a page sees the press. Prefer a key the browser leaves to the page.
- The overview lists what is bound when it opens. A shortcut whose element a later Livewire update adds is listed the next time the overview opens.
Design Tokens
| Token | Used for |
|---|---|
--color-wk-text |
A shortcut's name and the area headings |
--color-wk-text-muted |
"then" between the presses of a sequence, and the notes |
--color-wk-border-subtle |
The line between two shortcuts |
--text-wk-sm |
The names, the headings and the notes |
--gap-wk-lg |
The space between two areas |
--gap-wk-xs |
The space between keys |
--padding-wk-y-xs |
The space above and below a shortcut |
The keys themselves are Kbd, and the dialog is a Modal, each with its own tokens.
Further Reading
- Command Palette, whose
hotkeyopens a search for commands - Kbd, for a key shown in text
- WCAG 2.1.4: Character Key Shortcuts
- MDN:
aria-keyshortcuts