diff --git a/devTools/FC.ts b/devTools/FC.ts
index a3125ee3a5ec7815ea39d6a05b111eb68da4d2dc..0e0e0d1055519b5908e49ffc52af9029f75d87bf 100644
--- a/devTools/FC.ts
+++ b/devTools/FC.ts
@@ -256,6 +256,7 @@ namespace App {
 			declare function appendNewElement<K extends keyof HTMLElementTagNameMap>(tag: K, parent: ParentNode, content?: string | Node, classNames?: string | string[]): HTMLElementTagNameMap[K];
 		}
 		namespace View { }
+		namespace Navigation { }
 		namespace SlaveSummary {
 			type AppendRenderer = (slave: FC.SlaveState, parentNode: Node) => void;
 
diff --git a/src/002-config/mousetrapConfig.js b/src/002-config/mousetrapConfig.js
index df31dc5fb398961e29312bfaca764e0dab3ac096..720f0514fb2222e6d05c8f86013068f04352ce68 100644
--- a/src/002-config/mousetrapConfig.js
+++ b/src/002-config/mousetrapConfig.js
@@ -1,29 +1,137 @@
-/* eslint-disable */
-Mousetrap.bind("enter", function() {
-	$("#story-caption #endWeekButton a.macro-link").trigger("click");
+/**
+ * Expand mousetrap with multiple binds per key, passage dependent bindings and a menu for custom rebinding.
+ */
+
+App.UI.Navigation = (function() {
+	/**
+	 * @typedef action
+	 * @property {Function} callback
+	 * @property {Array<string>} combinations 0 <= length <= 2
+	 * @property {Array<string>} [passages] not existing means everywhere
+	 * @property {string|function(): string} [uiName] allow different name in hotkey settings
+	 */
+
+	/**
+	 * The key is used in the hotkey settings to describe the action
+	 * @type {object.<string, action>}
+	 */
+	const actions = {};
+
+	/**
+	 * References a key combination to a set of actions
+	 * @type {object.<string, Array<string>>}
+	 */
+	const bindings = {};
+
+	/**
+	 * @param {string} name used as key
+	 * @param {action} action
+	 */
+	function addDefault(name, action) {
+		actions[name] = action;
+		for (const binding of action.combinations) {
+			addBinding(name, binding);
+		}
+	}
+
+	/**
+	 * @param {string} action
+	 * @param {string} combination
+	 */
+	function addBinding(action, combination) {
+		if (bindings[combination]) {
+			bindings[combination].push(action);
+		} else {
+			bindings[combination] = [action];
+			Mousetrap.bind(combination, () => {
+				for (const binding of bindings[combination]) {
+					const action = actions[binding];
+					// only activate callback if we are on the right passage
+					if (!action.passages || action.passages.includes(State.passage)) {
+						action.callback();
+					}
+				}
+			});
+		}
+	}
+
+	/**
+	 * @param {string} combination
+	 */
+	function removeBinding(combination) {
+		if (bindings[combination]) {
+			const index = bindings[combination].indexOf(category);
+			if (index > -1) {
+				bindings[combination].splice(index, 1);
+			}
+			if (bindings[combination].length === 0) {
+				delete bindings[combination];
+				Mousetrap.unbind(combination);
+			}
+		}
+	}
+
+	function hotkeysForAction(name) {
+		if (!actions[name]) {
+			return "";
+		}
+		const c =actions[name].combinations
+		if (c.length === 0) {
+			return "";
+		}
+		// TODO make key combinations look nicer
+		if (c.length === 1) {
+			return `[${c[0]}]`;
+		}
+		return `[${c[0]},${c[1]}]`;
+	}
+
+	return {
+		add: addDefault,
+		hotkeys: hotkeysForAction,
+		//init: loadFromStorage,
+		//settings: settingsMenu,
+	};
+})();
+
+// add hotkeys
+App.UI.Navigation.add("Advance week", {
+	callback: function() {
+		$("#story-caption #endWeekButton a.macro-link").trigger("click");
+	}, combinations: ["enter"]
 });
-Mousetrap.bind("space", function() {
-	$("#story-caption #nextButton a.macro-link").trigger("click");
+App.UI.Navigation.add("Continue/Back", {
+	callback: function() {
+		$("#story-caption #nextButton a.macro-link").trigger("click");
+	}, combinations: ["space"]
 });
-Mousetrap.bind("left", function() {
-	$("#prevSlave a.macro-link").trigger("click");
-	$("#prevChild a.macro-link").trigger("click");
+App.UI.Navigation.add("Previous Slave", {
+	callback: function() {
+		$("#prevSlave a.macro-link").trigger("click");
+	}, combinations: ["left", "q"]
 });
-Mousetrap.bind("q", function() {
-	$("#prevSlave a.macro-link").trigger("click");
-	$("#prevChild a.macro-link").trigger("click");
+App.UI.Navigation.add("Next Slave", {
+	callback: function() {
+		$("#nextSlave a.macro-link").trigger("click");
+	}, combinations: ["right", "e"]
 });
-Mousetrap.bind("right", function() {
-	$("#nextSlave a.macro-link").trigger("click");
-	$("#nextChild a.macro-link").trigger("click");
+App.UI.Navigation.add("Previous Child", {
+	callback: function() {
+		$("#prevChild a.macro-link").trigger("click");
+	}, combinations: ["left", "q"]
 });
-Mousetrap.bind("e", function() {
-	$("#nextSlave a.macro-link").trigger("click");
-	$("#nextChild a.macro-link").trigger("click");
+App.UI.Navigation.add("Next Child", {
+	callback: function() {
+		$("#nextChild a.macro-link").trigger("click");
+	}, combinations: ["right", "e"]
 });
-Mousetrap.bind("f", function() {
-	$("#walkpast a.macro-link").trigger("click");
+App.UI.Navigation.add("walkpast", {
+	callback: function() {
+		$("#walkpast a.macro-link").trigger("click");
+	}, combinations: ["f"]
 });
-Mousetrap.bind("h", function() {
-	$("#manageHG a").trigger("click");
+App.UI.Navigation.add("Manage HG", {
+	callback: function() {
+		$("#manageHG a").trigger("click");
+	}, combinations: ["h"]
 });
diff --git a/src/gui/quicklinks.js b/src/gui/quicklinks.js
index 5fda6b661f6c7dc1a85bb4b9f96c3142d5a7b9de..0f23b17fdde43112042ec3e460f8f492739d0eb1 100644
--- a/src/gui/quicklinks.js
+++ b/src/gui/quicklinks.js
@@ -177,49 +177,6 @@ App.UI.quickMenu = (function() {
 		"Manage Corporation": () => V.corp.SpecToken > 0 && V.corp.SpecTimer === 0,
 	});
 
-	// setup hotkeys list, upper/lower case is important!
-	// Due to limitation to the key capture library keys cannot be used when they are already used in
-	// src/002-config/mousetrapConfig.js
-	const hotkeys = cleanPassageMapping({
-		"BG Select": "b",
-		"Buy Slaves": "s",
-		edicts: "E",
-		Firebase: "z",
-		"Future Society": "f",
-		Main: "m",
-		"Manage Arcology": "c",
-		"Manage Corporation":"C",
-		"Manage Penthouse": "p",
-		"Manage Personal Affairs": "x",
-		"Neighbor Interact": "d",
-		Options: "o",
-		"Personal assistant options": "t",
-		"Personal Attention Select": "a",
-		Policies: "y",
-		propagandaHub: "H",
-		"Recruiter Select": "u",
-		riotControlCenter: "R",
-		"Rules Assistant": "r",
-		secBarracks: "A",
-		securityHQ: "S",
-		"Universal Rules": "v",
-		// Facilities
-		Brothel: "1",
-		Club: "2",
-		Arcade: "3",
-		Dairy: "4",
-		Farmyard: "5",
-		"Servants' Quarters": "6",
-		"Master Suite": "7",
-		Schoolroom: "8",
-		Spa: "9",
-		Nursery: "0",
-		Clinic: "shift+1",
-		Cellblock: "shift+2",
-		Incubator: "shift+3",
-		Pit: "shift+4",
-	});
-
 	/**
 	 * The DOM element of name of the currently played passage or any of it's parents. Used during generation to
 	 * uncollapse the category with the current passage.
@@ -235,14 +192,73 @@ App.UI.quickMenu = (function() {
 	let hotkeysEnabled = false;
 
 	// register hotkeys
-	for (const passage in hotkeys) {
-		Mousetrap.bind(hotkeys[passage], () => {
-			if (hotkeysEnabled
-				// the passage is accessible
-				&& !(hiddenPassages[passage] && hiddenPassages[passage]())) {
-				Engine.play(passage);
-			}
+	// this is in it's own scope as we can forget the hotkeys object immediately afterwards
+	{
+		// setup hotkeys list, upper/lower case is important!
+		const hotkeys = cleanPassageMapping({
+			"BG Select": "b",
+			"Buy Slaves": "s",
+			edicts: "E",
+			Firebase: "z",
+			"Future Society": "f",
+			Main: "m",
+			"Manage Arcology": "c",
+			"Manage Corporation": "C",
+			"Manage Penthouse": "p",
+			"Manage Personal Affairs": "x",
+			"Neighbor Interact": "d",
+			Options: "o",
+			"Personal assistant options": "t",
+			"Personal Attention Select": "a",
+			Policies: "y",
+			propagandaHub: "H",
+			"Recruiter Select": "u",
+			riotControlCenter: "R",
+			"Rules Assistant": "r",
+			secBarracks: "A",
+			securityHQ: "S",
+			"Universal Rules": "v",
+			// Facilities
+			Brothel: "1",
+			Club: "2",
+			Arcade: "3",
+			Dairy: "4",
+			Farmyard: "5",
+			"Servants' Quarters": "6",
+			"Master Suite": "7",
+			Schoolroom: "8",
+			Spa: "9",
+			Nursery: "0",
+			Clinic: "shift+1",
+			Cellblock: "shift+2",
+			Incubator: "shift+3",
+			Pit: "shift+4",
 		});
+
+		// register
+		for (const passage of jumpTo) {
+			if (!hidden.includes(passage)) {
+				const action = {
+					callback: () => {
+						if (hotkeysEnabled
+							// the passage is accessible
+							&& !(hiddenPassages[passage] && hiddenPassages[passage]())) {
+							Engine.play(passage);
+						}
+					},
+					combinations: [],
+				};
+				// add hotkey if there is one
+				if (hotkeys[passage]) {
+					action.combinations.push(hotkeys[passage]);
+				}
+				// custom ui text
+				if (uiNames[passage]) {
+					action.uiName = uiNames[passage];
+				}
+				App.UI.Navigation.add(passage, action);
+			}
+		}
 	}
 
 	// setup history
@@ -268,10 +284,7 @@ App.UI.quickMenu = (function() {
 			history.shift();
 		}
 	});
-	Mousetrap.bind("backspace", () => {
-		// jump back in history
-		goBack();
-	});
+	App.UI.Navigation.add("Back in history", {callback: goBack, combinations: ["backspace"]});
 
 	/**
 	 * Goes back in history if possible.
@@ -447,8 +460,9 @@ App.UI.quickMenu = (function() {
 			Engine.play(passage);
 		};
 		div.prepend(a);
-		if (hotkeys[passage]) {
-			div.append(" ", App.UI.DOM.makeElement("span", `[${hotkeys[passage]}]`, "hotkey"));
+		const hotkeyString = App.UI.Navigation.hotkeys(passage);
+		if (hotkeyString !== "") {
+			div.append(" ", App.UI.DOM.makeElement("span", hotkeyString, "hotkey"));
 		}
 	}