[Feature]: Allow customising / remapping keyboard shortcuts (hotkeys)

Author: edkranzCreated Jul 11, 2026Updated Jul 11, 2026

Is your feature request related to a problem? Please describe.

Keyboard shortcuts in Stremio Web are currently fixed. The Settings → Shortcuts pane lists them, but there is no way to change any of them. This is a problem for a number of users:

  • People with non‑QWERTY / non‑US keyboard layouts, where some default keys are awkward or unreachable (e.g. [ / ] for playback speed, - / = for subtitle size).
  • Users coming from other players (mpv, VLC, YouTube, Jellyfin) who have muscle memory for a different set of keys and would like to match them.
  • Accessibility needs, where a user may need to move an action to a single, easy‑to‑reach key.
  • Conflicts with browser or OS shortcuts (see #203), which a user could resolve themselves if remapping were allowed.
  • Keys that are easy to hit by accident and have a disruptive effect. For example, I frequently hit 1 when I mean to hit Escape in the player — but 1 is bound to navigateTabs (["1"..."6"] in shortcuts.json), so instead of doing nothing it yanks me completely out of the media player to a top‑level nav tab.

Describe the solution you'd like

Allow users to customise / remap the existing keyboard shortcuts from the Settings → Shortcuts pane, with sensible guard‑rails.

The codebase is already well set up for this — the shortcut system is centralised and data‑driven:

  • src/common/Shortcuts/shortcuts.json — every action is already declared with a stable name, an i18n label, and a list of combos (e.g. playPause[["Space"]], seekForward[["ArrowRight"], ["Shift","ArrowRight"]]), grouped into general and player categories.
  • src/common/Shortcuts/Shortcuts.tsx — the provider statically imports shortcuts.json (import shortcuts from './shortcuts.json') and registers the combos. This is the single place that would need to merge user overrides over the defaults.
  • src/routes/Settings/Shortcuts/Shortcuts.tsx — already renders the grouped shortcuts (via useShortcuts()), but purely read‑only. This is where "click a shortcut → press new key(s) to rebind" UI would live.
  • src/common/Shortcuts/onShortcut.ts — consumers subscribe by action name, not by raw key, so remapping requires no changes at any call site (Player, App navigation, etc.). Only the default → active combo resolution changes.

Suggested behaviour:

  1. In Settings → Shortcuts, make each row editable: click a binding, capture the next key combo, and assign it to that action.
  2. Persist overrides (a { [shortcutName]: Combo[] } map) locally on the device, falling back to the defaults in shortcuts.json when there's no override.
  3. Conflict detection — warn (or block) when a combo is already bound to another action in the same context.
  4. Reset to default — per‑shortcut and a "reset all" button.

Describe alternatives you've considered

  • Editing shortcuts.json and rebuilding — only viable for people building from source; not a real option for normal users.
  • Browser extensions / userscripts to intercept keys — brittle, breaks with UI changes, and doesn't work in the desktop shell.

Additional context

Because the shortcut layer is already action‑based and centralised, the change is largely additive: an overrides store + a merge step in Shortcuts.tsx + an editing affordance in the existing Settings pane. No consumer of onShortcut(...) needs to change.

Related: #203 (shortcuts conflicting with browser shortcuts), #75 (global actions).


Implementation scope (v1)

To keep this contained entirely within stremio-web (no stremio-core / WASM changes), the first pass is deliberately minimal:

  • Storage: overrides are persisted in localStorage (per‑device), not in account/profile settings. This avoids adding a field to the core Settings struct. Profile‑level sync can be a follow‑up — the storage layer is isolated to a couple of functions, so swapping it later is straightforward.
  • Per‑binding editing: shortcuts with multiple bindings (e.g. Volume /, Seek / Shift+→) are edited one binding at a time, preserving the others.
  • Conflict detection & reset: included (per‑shortcut reset + "reset all").

Out of scope for v1 (possible follow‑ups):

  • Not synced across devices (localStorage).
  • navigateTabs (the 16 tab range) is read‑only. Its keys are hard‑wired to tab indices in the consumer (Number(key) - 1 in App.js), so remapping/unbinding it would require changing that call site. Solving the "accidental 1 in the player" case (unbind it, or suppress global tab navigation while the player is focused) is a separate, small follow‑up.