Implementing keyboard shortcuts

We use Mousetrap to implement keyboard shortcuts in GitLab.

Mousetrap provides an API that allows keyboard shortcut strings (like mod+shift+p or p b) to be bound to a JavaScript handler:

// Don't do this; see note below
Mousetrap.bind('p b', togglePerformanceBar)

However, associating a hard-coded key sequence to a handler (as shown above) prevents these keyboard shortcuts from being customized or disabled by users.

To allow keyboard shortcuts to be customized, commands are defined in ~/behaviors/shortcuts/keybindings.js. The keysFor method is responsible for returning the correct key sequence for the provided command:

import { keysFor, TOGGLE_PERFORMANCE_BAR } from '~/behaviors/shortcuts/keybindings'

Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), togglePerformanceBar);

Shortcut customization

keybindings.js stores keyboard shortcut customizations as a JSON string in localStorage. When keybindings.js is first imported, it fetches any customizations from localStorage and merges these customizations into the default set of keybindings. There is no UI to edit these customizations.

Adding new shortcuts

Because keyboard shortcuts can be customized or disabled by end users, developers are encouraged to build lots of keyboard shortcuts into GitLab. Shortcuts that are less likely to be used should be disabled by default.

To add a new shortcut, define and export a new command string in keybindings.js:

export const MAKE_COFFEE = 'foodAndBeverage.makeCoffee';

Next, add a new command definition under the appropriate group in the keybindingGroups array:

{
  description: s__('KeyboardShortcuts|Make coffee'),
  command: MAKE_COFFEE,
  defaultKeys: ['mod+shift+c'],
  customKeys: customizations[MAKE_COFFEE],
}

Finally, in the application code, import the keysFor function and the new command and bind the shortcut to the handler using Mousetrap:

import { keysFor, MAKE_COFFEE } from '~/behaviors/shortcuts/keybindings'

Mousetrap.bind(keysFor(MAKE_COFFEE), makeCoffee);

See the existing the command definitions in keybindings.js for more examples.

Disabling shortcuts

A shortcut can be disabled, also known as unassigned, by assigning the shortcut to an empty array []. For example, to introduce a new shortcut that is disabled by default, a command can be defined like this:

export const MAKE_MOCHA = 'foodAndBeverage.makeMocha';

{
  description: s__('KeyboardShortcuts|Make a mocha'),
  command: MAKE_MOCHA,
  defaultKeys: [],
  customKeys: customizations[MAKE_MOCHA],
}

Make cross-platform shortcuts

It’s difficult to make shortcuts that work well in all platforms and browsers. This is one of the reasons that being able to customize and disable shortcuts is so important.

One important way to make keyboard shortcuts more portable is to use the mod shortcut string, which resolves to command on Mac and ctrl otherwise.

See Mousetrap’s documentation for more information.