Shortcut Recorder — Capture Keyboard Hotkeys in a Field
Shortcut Recorder · Forms · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="sk-card">
<h3>Keyboard shortcuts</h3>
<ul class="sk-list" id="skList">
<li class="sk-row" data-action="Save"><span>Save</span><button type="button" class="sk-field" data-combo="Ctrl+S"></button></li>
<li class="sk-row" data-action="Command palette"><span>Command palette</span><button type="button" class="sk-field" data-combo="Ctrl+K"></button></li>
<li class="sk-row" data-action="New item"><span>New item</span><button type="button" class="sk-field" data-combo=""></button></li>
</ul>
<p class="sk-hint" id="skHint">Click a shortcut, then press a key combination.</p>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0f172a;color:#e2e8f0;display:flex;justify-content:center;padding:40px 20px}
.sk-card{background:#1e293b;border:1px solid #334155;border-radius:18px;padding:22px;width:100%;max-width:380px}
.sk-card h3{font-size:16px;font-weight:800;color:#f1f5f9;margin-bottom:14px}
.sk-list{list-style:none;display:flex;flex-direction:column;gap:8px}
.sk-row{display:flex;align-items:center;justify-content:space-between;gap:14px}
.sk-row>span{font-size:13.5px;font-weight:600;color:#cbd5e1}
.sk-field{min-width:128px;text-align:center;background:#0f172a;border:1px solid #334155;border-radius:9px;padding:7px 10px;font-family:inherit;font-size:12px;color:#94a3b8;cursor:pointer;transition:border-color .15s,box-shadow .15s}
.sk-field:hover{border-color:#475569}
.sk-field.sk-recording{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.25);color:#a5b4fc}
.sk-field.sk-dupe{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.2)}
.sk-field .sk-kbd{display:inline-block;background:#334155;color:#e2e8f0;border-radius:5px;padding:2px 7px;font-size:11px;font-weight:700;margin:0 2px;font-family:ui-monospace,monospace}
.sk-field .sk-plus{color:#475569;margin:0 1px}
.sk-hint{margin-top:14px;font-size:12px;color:#64748b;min-height:18px}var fields = Array.prototype.slice.call(document.querySelectorAll('.sk-field'));
var hint = document.getElementById('skHint');
var recording = null;
var MODS = ['Control', 'Alt', 'Shift', 'Meta'];
var PRETTY = { Control: 'Ctrl', Meta: navigator.platform.indexOf('Mac') > -1 ? 'Cmd' : 'Win', ' ': 'Space', ArrowUp: '↑', ArrowDown: '↓', ArrowLeft: '←', ArrowRight: '→', Escape: 'Esc' };
function renderCombo(field) {
var combo = field.getAttribute('data-combo');
if (!combo) { field.textContent = field.classList.contains('sk-recording') ? 'Press keys…' : 'Not set'; return; }
field.innerHTML = combo.split('+').map(function (k, i) {
return (i ? '<span class="sk-plus">+</span>' : '') + '<span class="sk-kbd">' + k + '</span>';
}).join('');
}
function isDuplicate(combo, self) {
return fields.some(function (f) { return f !== self && f.getAttribute('data-combo') === combo && combo; });
}
function stop() {
if (!recording) return;
recording.classList.remove('sk-recording');
renderCombo(recording);
recording = null;
hint.textContent = 'Click a shortcut, then press a key combination.';
}
function start(field) {
stop();
recording = field;
field.classList.add('sk-recording');
renderCombo(field);
hint.textContent = 'Recording… press Esc to cancel.';
}
fields.forEach(function (field) {
renderCombo(field);
field.addEventListener('click', function () { recording === field ? stop() : start(field); });
});
document.addEventListener('keydown', function (e) {
if (!recording) return;
e.preventDefault();
if (e.key === 'Escape') { stop(); return; }
if (MODS.indexOf(e.key) > -1) return; // wait for a non-modifier key
var parts = [];
if (e.ctrlKey) parts.push('Ctrl');
if (e.metaKey) parts.push(PRETTY.Meta);
if (e.altKey) parts.push('Alt');
if (e.shiftKey) parts.push('Shift');
var key = PRETTY[e.key] || (e.key.length === 1 ? e.key.toUpperCase() : e.key);
parts.push(key);
var combo = parts.join('+');
recording.setAttribute('data-combo', combo);
var dupe = isDuplicate(combo, recording);
recording.classList.toggle('sk-dupe', dupe);
var field = recording;
stop();
field.classList.toggle('sk-dupe', dupe);
renderCombo(field);
hint.textContent = dupe ? 'That combination is already used.' : 'Saved ' + combo + '.';
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Shortcut Recorder — Capture and Edit Keyboard Hotkeys with Conflict Detection

A shortcut recorder lets users rebind a keyboard hotkey by simply pressing the combination they want — the control behind every "customise keyboard shortcuts" settings screen in editors, IDEs, and power-user apps. This snippet captures a live key chord, renders it as kbd chips, normalises modifiers per platform, and flags duplicates, in plain HTML, CSS, and vanilla JavaScript.
Click to record, press to set
Each shortcut is a button showing its current binding. Click it and it enters recording mode (a glowing focus ring and "Press keys…" prompt); the next key combination you press is captured and saved. Pressing Escape cancels without changing anything. Clicking the field again toggles recording off. This click-then-press flow is exactly how native preference panes capture shortcuts.
Reading a chord correctly
The trick to capturing chords is ignoring lone modifier presses. When keydown fires for Control, Shift, Alt, or Meta by itself, the recorder waits — a shortcut isn't complete until a non-modifier key arrives. When a real key lands, it reads the modifier flags (ctrlKey, metaKey, altKey, shiftKey) and assembles them in a consistent order, then appends the key. preventDefault() stops the browser acting on combos like Ctrl+S while you're recording.
Platform-aware labels
Modifiers are prettified for humans: Control becomes "Ctrl", and Meta becomes "Cmd" on macOS or "Win" elsewhere via a quick platform check. Special keys map to symbols — arrows to ↑↓←→, Escape to "Esc", space to "Space" — and single character keys are upper-cased so "k" displays as "K". The result renders as individual kbd chips joined by +, the familiar way shortcuts appear in menus and docs.
Duplicate detection
After a binding is set, the recorder checks whether any other shortcut already uses the same combination and flags both with a red ring and a hint ("That combination is already used"). Catching conflicts at capture time is what separates a real shortcut editor from a text field — two actions bound to the same chord is a bug users can't diagnose themselves.
Portable and minimal
The captured value is a plain string like Ctrl+K stored in a data attribute, trivial to persist or feed into a hotkey library. The whole recorder is one keydown handler and a small render function with no dependencies — a clean reference for building keyboard-shortcut customisation into any settings screen.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA list of actions renders, each with its current shortcut.
- 2Click a shortcutThe field starts recording and prompts you to press keys.
- 3Press a combinationHold modifiers and press a key — it captures as Ctrl+K style chips.
- 4Cancel if neededPress Esc while recording to leave the binding unchanged.
- 5Watch for conflictsA red ring warns when a combo is already used by another action.
- 6Read the valueEach field stores its combo string in data-combo — persist it as you like.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
On keydown while recording, it ignores presses that are only a modifier (Control, Shift, Alt, Meta) and waits for a real key. When one arrives, it reads ctrlKey/metaKey/altKey/shiftKey, assembles them in a fixed order, appends the key, and saves the result. It also calls preventDefault so the browser does not act on combos like Ctrl+S during capture.
Yes. The Meta key renders as "Cmd" on macOS and "Win" on other platforms via a navigator.platform check, and Control always shows as "Ctrl". You can extend the PRETTY map to localise further or to display the actual ⌘/⌥/⇧/⌃ glyphs if you prefer the macOS style.
After a binding is set, the recorder scans the other fields for the same combo string. If it finds one, it adds a red ring to the field and shows a hint that the combination is already used. You decide the policy — warn only (as here), block the save, or swap the binding away from the other action.
A plain string such as "Ctrl+K" or "Cmd+Shift+P", held in the field's data-combo attribute. It is easy to persist to storage or a backend, render in menus, and feed into a hotkey matcher at runtime by comparing it against the same modifier flags on keydown.
Hold each binding in state and render the chips from it. Track a "recording" id in state, attach a window keydown listener while recording (in an effect that cleans up), and update the binding when a non-modifier key arrives. Compute the duplicate flag as derived state. Tailwind users replace the classes with utilities; the keydown logic is identical across frameworks.