diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php
index cb6082c52..5e1e4348a 100644
--- a/app/Config/setting-defaults.php
+++ b/app/Config/setting-defaults.php
@@ -26,6 +26,8 @@ return [
 
     // User-level default settings
     'user' => [
+        'ui-shortcuts'          => '{}',
+        'ui-shortcuts-enabled'  => false,
         'dark-mode-enabled'     => env('APP_DEFAULT_DARK_MODE', false),
         'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
         'bookshelf_view_type'   => env('APP_VIEWS_BOOKSHELF', 'grid'),
diff --git a/app/Http/Controllers/UserPreferencesController.php b/app/Http/Controllers/UserPreferencesController.php
index 972742e03..b8bb31468 100644
--- a/app/Http/Controllers/UserPreferencesController.php
+++ b/app/Http/Controllers/UserPreferencesController.php
@@ -3,6 +3,7 @@
 namespace BookStack\Http\Controllers;
 
 use BookStack\Auth\UserRepo;
+use BookStack\Settings\UserShortcutMap;
 use Illuminate\Http\Request;
 
 class UserPreferencesController extends Controller
@@ -14,6 +15,37 @@ class UserPreferencesController extends Controller
         $this->userRepo = $userRepo;
     }
 
+    /**
+     * Show the user-specific interface shortcuts.
+     */
+    public function showShortcuts()
+    {
+        $shortcuts = UserShortcutMap::fromUserPreferences();
+        $enabled = setting()->getForCurrentUser('ui-shortcuts-enabled', false);
+
+        return view('users.preferences.shortcuts', [
+            'shortcuts' => $shortcuts,
+            'enabled' => $enabled,
+        ]);
+    }
+
+    /**
+     * Update the user-specific interface shortcuts.
+     */
+    public function updateShortcuts(Request $request)
+    {
+        $enabled = $request->get('enabled') === 'true';
+        $providedShortcuts = $request->get('shortcuts', []);
+        $shortcuts = new UserShortcutMap($providedShortcuts);
+
+        setting()->putUser(user(), 'ui-shortcuts', $shortcuts->toJson());
+        setting()->putUser(user(), 'ui-shortcuts-enabled', $enabled);
+
+        $this->showSuccessNotification('Shortcuts preferences have been updated!');
+
+        return redirect('/preferences/shortcuts');
+    }
+
     /**
      * Update the user's preferred book-list display setting.
      */
diff --git a/app/Settings/UserShortcutMap.php b/app/Settings/UserShortcutMap.php
new file mode 100644
index 000000000..da2ea3c10
--- /dev/null
+++ b/app/Settings/UserShortcutMap.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace BookStack\Settings;
+
+class UserShortcutMap
+{
+    protected const DEFAULTS = [
+        // Header actions
+        "home_view" => "1",
+        "shelves_view" => "2",
+        "books_view" => "3",
+        "settings_view" => "4",
+        "favourites_view" => "5",
+        "profile_view" => "6",
+        "global_search" => "/",
+        "logout" => "0",
+
+        // Common actions
+        "edit" => "e",
+        "new" => "n",
+        "copy" => "c",
+        "delete" => "d",
+        "favourite" => "f",
+        "export" => "x",
+        "sort" => "s",
+        "permissions" => "p",
+        "move" => "m",
+        "revisions" => "r",
+
+        // Navigation
+        "next" => "ArrowRight",
+        "previous" => "ArrowLeft",
+    ];
+
+    /**
+     * @var array<string, string>
+     */
+    protected array $mapping;
+
+    public function __construct(array $map)
+    {
+        $this->mapping = static::DEFAULTS;
+        $this->merge($map);
+    }
+
+    /**
+     * Merge the given map into the current shortcut mapping.
+     */
+    protected function merge(array $map): void
+    {
+        foreach ($map as $key => $value) {
+            if (is_string($value) && isset($this->mapping[$key])) {
+                $this->mapping[$key] = $value;
+            }
+        }
+    }
+
+    /**
+     * Get the shortcut defined for the given ID.
+     */
+    public function getShortcut(string $id): string
+    {
+        return $this->mapping[$id] ?? '';
+    }
+
+    /**
+     * Convert this mapping to JSON.
+     */
+    public function toJson(): string
+    {
+        return json_encode($this->mapping);
+    }
+
+    /**
+     * Create a new instance from the current user's preferences.
+     */
+    public static function fromUserPreferences(): self
+    {
+        $userKeyMap = setting()->getForCurrentUser('ui-shortcuts');
+        return new self(json_decode($userKeyMap, true) ?: []);
+    }
+}
diff --git a/resources/icons/shortcuts.svg b/resources/icons/shortcuts.svg
new file mode 100644
index 000000000..8d23aac2b
--- /dev/null
+++ b/resources/icons/shortcuts.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm8 7H9c-.55 0-1-.45-1-1s.45-1 1-1h6c.55 0 1 .45 1 1s-.45 1-1 1zm1-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z"/></svg>
\ No newline at end of file
diff --git a/resources/js/components/shortcuts.js b/resources/js/components/shortcuts.js
index a3cca5ddc..ccad00f5d 100644
--- a/resources/js/components/shortcuts.js
+++ b/resources/js/components/shortcuts.js
@@ -1,35 +1,3 @@
-/**
- * The default mapping of unique id to shortcut key.
- * @type {Object<string, string>}
- */
-const defaultMap = {
-    // Header actions
-    "home": "1",
-    "shelves_view": "2",
-    "books_view": "3",
-    "settings_view": "4",
-    "favorites_view": "5",
-    "profile_view": "6",
-    "global_search": "/",
-    "logout": "0",
-
-    // Generic actions
-    "edit": "e",
-    "new": "n",
-    "copy": "c",
-    "delete": "d",
-    "favorite": "f",
-    "export": "x",
-    "sort": "s",
-    "permissions": "p",
-    "move": "m",
-    "revisions": "r",
-
-    // Navigation
-    "next": "ArrowRight",
-    "prev": "ArrowLeft",
-};
-
 function reverseMap(map) {
     const reversed = {};
     for (const [key, value] of Object.entries(map)) {
@@ -45,14 +13,12 @@ class Shortcuts {
 
     setup() {
         this.container = this.$el;
-        this.mapById = defaultMap;
+        this.mapById = JSON.parse(this.$opts.keyMap);
         this.mapByShortcut = reverseMap(this.mapById);
 
         this.hintsShowing = false;
 
         this.hideHints = this.hideHints.bind(this);
-        // TODO - Allow custom key maps
-        // TODO - Allow turning off shortcuts
 
         this.setupListeners();
     }
diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss
index 7e0f72355..7de8a9d7d 100644
--- a/resources/sass/_forms.scss
+++ b/resources/sass/_forms.scss
@@ -473,4 +473,10 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
 .custom-file-input:focus + label {
   border-color: var(--color-primary);
   outline: 1px solid var(--color-primary);
+}
+
+input.shortcut-input {
+  width: auto;
+  max-width: 120px;
+  height: auto;
 }
\ No newline at end of file
diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php
index 0481b3412..dd463b76e 100644
--- a/resources/views/common/header.blade.php
+++ b/resources/views/common/header.blade.php
@@ -2,7 +2,7 @@
     <div class="grid mx-l">
 
         <div>
-            <a href="{{ url('/') }}" data-shortcut="home" class="logo">
+            <a href="{{ url('/') }}" data-shortcut="home_view" class="logo">
                 @if(setting('app-logo', '') !== 'none')
                     <img class="logo-image" src="{{ setting('app-logo', '') === '' ? url('/logo.png') : url(setting('app-logo', '')) }}" alt="Logo">
                 @endif
@@ -62,7 +62,7 @@
                         </span>
                     <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
                         <li>
-                            <a href="{{ url('/favourites') }}" data-shortcut="favorites_view" class="icon-item">
+                            <a href="{{ url('/favourites') }}" data-shortcut="favourites_view" class="icon-item">
                                 @icon('star')
                                 <div>{{ trans('entities.my_favourites') }}</div>
                             </a>
@@ -90,6 +90,12 @@
                             </form>
                         </li>
                         <li><hr></li>
+                        <li>
+                            <a href="{{ url('/preferences/shortcuts') }}" class="icon-item">
+                                @icon('shortcuts')
+                                <div>{{ 'Shortcuts' }}</div>
+                            </a>
+                        </li>
                         <li>
                             @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
                         </li>
diff --git a/resources/views/entities/favourite-action.blade.php b/resources/views/entities/favourite-action.blade.php
index f38899e3e..24bd40950 100644
--- a/resources/views/entities/favourite-action.blade.php
+++ b/resources/views/entities/favourite-action.blade.php
@@ -5,7 +5,7 @@
     {{ csrf_field() }}
     <input type="hidden" name="type" value="{{ get_class($entity) }}">
     <input type="hidden" name="id" value="{{ $entity->id }}">
-    <button type="submit" data-shortcut="favorite" class="icon-list-item text-primary">
+    <button type="submit" data-shortcut="favourite" class="icon-list-item text-primary">
         <span>@icon($isFavourite ? 'star' : 'star-outline')</span>
         <span>{{ $isFavourite ? trans('common.unfavourite') : trans('common.favourite') }}</span>
     </button>
diff --git a/resources/views/entities/sibling-navigation.blade.php b/resources/views/entities/sibling-navigation.blade.php
index 629a8ff32..28a9cb029 100644
--- a/resources/views/entities/sibling-navigation.blade.php
+++ b/resources/views/entities/sibling-navigation.blade.php
@@ -1,7 +1,7 @@
 <div id="sibling-navigation" class="grid half collapse-xs items-center mb-m px-m no-row-gap fade-in-when-active print-hidden">
     <div>
         @if($previous)
-            <a href="{{  $previous->getUrl()  }}" data-shortcut="prev" class="outline-hover no-link-style block rounded">
+            <a href="{{  $previous->getUrl()  }}" data-shortcut="previous" class="outline-hover no-link-style block rounded">
                 <div class="px-m pt-xs text-muted">{{ trans('common.previous') }}</div>
                 <div class="inline-block">
                     <div class="icon-list-item no-hover">
diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php
index 928eb17a0..2f649423d 100644
--- a/resources/views/layouts/base.blade.php
+++ b/resources/views/layouts/base.blade.php
@@ -31,7 +31,12 @@
     <!-- Translations for JS -->
     @stack('translations')
 </head>
-<body component="shortcuts" class="@stack('body-class')">
+<body
+    @if(setting()->getForCurrentUser('ui-shortcuts-enabled', false))
+        component="shortcuts"
+        option:shortcuts:key-map="{{ \BookStack\Settings\UserShortcutMap::fromUserPreferences()->toJson() }}"
+    @endif
+      class="@stack('body-class')">
 
     @include('layouts.parts.base-body-start')
     @include('common.skip-to-content')
diff --git a/resources/views/users/preferences/parts/shortcut-control.blade.php b/resources/views/users/preferences/parts/shortcut-control.blade.php
new file mode 100644
index 000000000..47fec3a5e
--- /dev/null
+++ b/resources/views/users/preferences/parts/shortcut-control.blade.php
@@ -0,0 +1,11 @@
+<div class="flex-container-row justify-space-between items-center gap-m item-list-row">
+    <label for="shortcut-{{ $label }}" class="bold flex px-m py-xs">{{ $label }}</label>
+    <div class="px-m py-xs">
+        <input type="text"
+               class="small flex-none shortcut-input px-s py-xs"
+               id="shortcut-{{ $id }}"
+               name="shortcut[{{ $id }}]"
+               readonly
+               value="{{ $shortcuts->getShortcut($id) }}">
+    </div>
+</div>
\ No newline at end of file
diff --git a/resources/views/users/preferences/shortcuts.blade.php b/resources/views/users/preferences/shortcuts.blade.php
new file mode 100644
index 000000000..9bb8e8175
--- /dev/null
+++ b/resources/views/users/preferences/shortcuts.blade.php
@@ -0,0 +1,78 @@
+@extends('layouts.simple')
+
+@section('body')
+    <div class="container small my-xl">
+
+        <section class="card content-wrap">
+            <form action="{{ url('/preferences/shortcuts') }}" method="post">
+                {{ method_field('put') }}
+                {{ csrf_field() }}
+
+                <h1 class="list-heading">Interface Keyboard Shortcuts</h1>
+
+                <div class="flex-container-row items-center gap-m wrap mb-m">
+                    <p class="flex mb-none min-width-m text-small text-muted">
+                        Here you can enable or disable keyboard system interface shortcuts, used for navigation
+                        and actions. You can customize each of the shortcuts below.
+                    </p>
+                    <div class="flex min-width-m text-m-right">
+                        @include('form.toggle-switch', [
+                            'name' => 'enabled',
+                            'value' => $enabled,
+                            'label' => 'Keyboard shortcuts enabled',
+                        ])
+                    </div>
+                </div>
+
+                <hr>
+
+                <h2 class="list-heading mb-m">Navigation</h2>
+                <div class="flex-container-row wrap gap-m mb-xl">
+                    <div class="flex min-width-l item-list">
+                        @include('users.preferences.parts.shortcut-control', ['label' => 'Homepage', 'id' => 'home_view'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.shelves'), 'id' => 'shelves_view'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.books'), 'id' => 'books_view'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('settings.settings'), 'id' => 'settings_view'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.my_favourites'), 'id' => 'favourites_view'])
+                    </div>
+                    <div class="flex min-width-l item-list">
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.view_profile'), 'id' => 'profile_view'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('auth.logout'), 'id' => 'logout'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => 'Global Search', 'id' => 'global_search'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.next'), 'id' => 'next'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.previous'), 'id' => 'previous'])
+                    </div>
+                </div>
+
+                <h2 class="list-heading mb-m">Common Actions</h2>
+                <div class="flex-container-row wrap gap-m mb-xl">
+                    <div class="flex min-width-l item-list">
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.new'), 'id' => 'new'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.edit'), 'id' => 'edit'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.copy'), 'id' => 'copy'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.delete'), 'id' => 'delete'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.favourite'), 'id' => 'favourite'])
+                    </div>
+                    <div class="flex min-width-l item-list">
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.export'), 'id' => 'export'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.sort'), 'id' => 'sort'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.permissions'), 'id' => 'permissions'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('common.move'), 'id' => 'move'])
+                        @include('users.preferences.parts.shortcut-control', ['label' => trans('entities.revisions'), 'id' => 'revisions'])
+                    </div>
+                </div>
+
+                <p class="text-small text-muted">
+                    Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will
+                    highlight the available shortcuts for actions currently visible on the screen.
+                </p>
+
+                <div class="form-group text-right">
+                    <button class="button">{{ 'Save Shortcuts' }}</button>
+                </div>
+
+            </form>
+        </section>
+
+    </div>
+@stop
diff --git a/routes/web.php b/routes/web.php
index b3f11f53a..f9899dba6 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -246,6 +246,9 @@ Route::middleware('auth')->group(function () {
     Route::delete('/settings/users/{id}', [UserController::class, 'destroy']);
 
     // User Preferences
+    Route::redirect('/preferences', '/');
+    Route::get('/preferences/shortcuts', [UserPreferencesController::class, 'showShortcuts']);
+    Route::put('/preferences/shortcuts', [UserPreferencesController::class, 'updateShortcuts']);
     Route::patch('/settings/users/{id}/switch-books-view', [UserPreferencesController::class, 'switchBooksView']);
     Route::patch('/settings/users/{id}/switch-shelves-view', [UserPreferencesController::class, 'switchShelvesView']);
     Route::patch('/settings/users/{id}/switch-shelf-view', [UserPreferencesController::class, 'switchShelfView']);