diff --git a/code/_globalvars/tgui.dm b/code/_globalvars/tgui.dm
index 6174a78a4ce..a81078420f5 100644
--- a/code/_globalvars/tgui.dm
+++ b/code/_globalvars/tgui.dm
@@ -1,3 +1,4 @@
GLOBAL_DATUM(crew_manifest_tgui, /datum/crew_manifest)
GLOBAL_DATUM(changelog_tgui, /datum/changelog)
+GLOBAL_DATUM(hotkeys_tgui, /datum/hotkeys_help)
GLOBAL_DATUM(record_manifest_tgui, /datum/record_manifest) //SKYRAT EDIT ADDITION -- EXPLOITABLE VERB
diff --git a/code/datums/hotkeys_help.dm b/code/datums/hotkeys_help.dm
new file mode 100644
index 00000000000..1f388a264c2
--- /dev/null
+++ b/code/datums/hotkeys_help.dm
@@ -0,0 +1,40 @@
+/datum/hotkeys_help
+ var/static/list/hotkeys = list()
+
+/datum/hotkeys_help/ui_state()
+ return GLOB.always_state
+
+/datum/hotkeys_help/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "HotkeysHelp")
+ ui.open()
+
+// Not static data since user could rebind keys.
+/datum/hotkeys_help/ui_data(mob/user)
+ // List every keybind to chat.
+ var/list/keys_list = list()
+
+ // Show them in alphabetical order by key
+ var/list/key_bindings_by_key = user.client.prefs.key_bindings_by_key.Copy()
+ sortTim(key_bindings_by_key, cmp = /proc/cmp_text_asc)
+
+ for(var/key in key_bindings_by_key)
+ // Get the full names
+ var/list/binding_names = list()
+ for(var/kb_name in key_bindings_by_key[key])
+ var/datum/keybinding/binding = GLOB.keybindings_by_name[kb_name]
+ binding_names += list(list(
+ "name" = binding.full_name,
+ "desc" = binding.description
+ ))
+
+ // Add to list
+ keys_list += list(list(
+ "key" = key,
+ "bindings" = binding_names
+ ))
+
+ return list(
+ "hotkeys" = keys_list
+ )
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 161e8420b0d..35b841beaf8 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1161,6 +1161,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if (verbpath.name[1] != "@")
new child(src)
+ // Place Help back at the end.
+ winset(src, "help-menu", "index=1000")
+
/client/proc/open_filter_editor(atom/in_atom)
if(holder)
holder.filteriffic = new /datum/filter_editor(in_atom)
diff --git a/interface/interface.dm b/interface/interface.dm
index f092a43fe33..a32bb758a20 100644
--- a/interface/interface.dm
+++ b/interface/interface.dm
@@ -109,3 +109,12 @@
prefs.lastchangelog = GLOB.changelog_hash
prefs.save_preferences()
winset(src, "infowindow.changelog", "font-style=;")
+
+/client/verb/hotkeys_help()
+ set name = "Hotkeys Help"
+ set category = "OOC"
+
+ if(!GLOB.hotkeys_tgui)
+ GLOB.hotkeys_tgui = new /datum/hotkeys_help()
+
+ GLOB.hotkeys_tgui.ui_interact(mob)
diff --git a/interface/skin.dmf b/interface/skin.dmf
index bad4154f239..f29bffeb22f 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -39,7 +39,7 @@ menu "menu"
command = ".quit"
category = "&File"
saved-params = "is-checked"
- elem
+ elem "help-menu"
name = "&Help"
command = ""
saved-params = "is-checked"
@@ -50,7 +50,7 @@ menu "menu"
saved-params = "is-checked"
elem
name = "&Hotkeys"
- command = "hotkeys-help"
+ command = "Hotkeys-Help"
category = "&Help"
saved-params = "is-checked"
diff --git a/tgstation.dme b/tgstation.dme
index aad53e2413b..301e3608a9b 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -612,6 +612,7 @@
#include "code\datums\forced_movement.dm"
#include "code\datums\hailer_phrase.dm"
#include "code\datums\holocall.dm"
+#include "code\datums\hotkeys_help.dm"
#include "code\datums\http.dm"
#include "code\datums\hud.dm"
#include "code\datums\map_config.dm"
diff --git a/tgui/packages/tgui/interfaces/HotkeysHelp.tsx b/tgui/packages/tgui/interfaces/HotkeysHelp.tsx
new file mode 100644
index 00000000000..4d67927abfd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/HotkeysHelp.tsx
@@ -0,0 +1,135 @@
+import { useBackend } from '../backend';
+import {
+ Box,
+ Section,
+ Table,
+ Tooltip,
+} from '../components';
+import { Window } from '../layouts';
+
+type BindingInfo = {
+ name: string;
+ desc: string;
+}
+
+type HotkeyInfo = {
+ key: string;
+ bindings: BindingInfo[];
+}
+
+type HotkeysHelpData = {
+ hotkeys: HotkeyInfo[];
+};
+
+type KeyBindingBoxProps = {
+ keycode: string,
+}
+
+type ModkeyProps = {
+ text: string,
+ color: string
+}
+
+const shiftRegex = /(.*)(Shift)(.*)/;
+const ctrlRegex = /(.*)(Ctrl)(.*)/;
+const altRegex = /(.*)(Alt)(.*)/;
+
+const addColorModifier = (content: string, regex: RegExp, color: string):
+ JSX.Element | null => {
+ const match = content.match(regex);
+
+ if (match) {
+ return (
+ <>
+ {processColorModifiers(match[1])}
+ {match[2]}
+ {processColorModifiers(match[3])}
+ >
+ );
+ }
+
+ return null;
+};
+
+const processColorModifiers = (content: string): string | JSX.Element => {
+ const shifted = addColorModifier(content, shiftRegex, "#88f");
+
+ if (shifted) {
+ return shifted;
+ }
+
+ const ctrled = addColorModifier(content, ctrlRegex, "#8f8");
+
+ if (ctrled) {
+ return ctrled;
+ }
+
+ const alted = addColorModifier(content, altRegex, "#fc4");
+
+ if (alted) {
+ return alted;
+ }
+
+ // Fix the weirdly named keys
+
+ return ` ${content}`
+ .replace("Northeast", "Page Up")
+ .replace("Southeast", "Page Down")
+ .replace("Northwest", "Home")
+ .replace("Southwest", "End")
+ .replace("North", "Up")
+ .replace("South", "Down")
+ .replace("East", "Right")
+ .replace("West", "Left")
+ .replace("Numpad", "Numpad ");
+};
+
+const KeyBinding = (props: KeyBindingBoxProps) => (
+ <>{processColorModifiers(props.keycode)}>
+);
+
+export const HotkeysHelp = (_, context) => {
+ const { data } = useBackend(context);
+
+ return (
+
+
+
+
+
+
+ Key
+
+
+ Binding
+
+
+ {data.hotkeys.map(hotkey => (
+
+
+
+
+
+ {hotkey.bindings.map(binding => (
+ binding.desc
+ ? (
+
+
+ {binding.name}
+
+
+ ) : (
+
+ {binding.name}
+
+ )
+ ))}
+
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/interfaces/HotKeysHelp.scss b/tgui/packages/tgui/styles/interfaces/HotKeysHelp.scss
new file mode 100644
index 00000000000..6bc74bcabfd
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/HotKeysHelp.scss
@@ -0,0 +1,4 @@
+.HotkeysHelp__pill {
+ border-radius: 5px;
+ background-color: rgba(255, 255, 255, 0.125);
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index 33c746f0eab..4ec9163a32a 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -54,6 +54,7 @@
@include meta.load-css('./interfaces/CrewManifest.scss');
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
@include meta.load-css('./interfaces/HellishRunes.scss');
+@include meta.load-css('./interfaces/HotkeysHelp.scss');
@include meta.load-css('./interfaces/Hypertorus.scss');
@include meta.load-css('./interfaces/LibraryComputer.scss');
@include meta.load-css('./interfaces/NuclearBomb.scss');