AZERTY digit hotkeys (2/7/9) broken by letter-key early return
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 719
- Forks
- 47
- Avg merge
- 5d 5h
- Merged PRs (30d)
- 1
Description
TanStack Hotkeys version
v0.8.0 (via @tanstack/react-hotkeys v0.10.0)
Framework/Library version
React 18
Describe the bug and the steps to reproduce it
On a French AZERTY keyboard, the unshifted number row produces accented letters instead of digits for several keys: 2→é, 7→è, 9→ç (the rest, e.g. 1→&, 3→", 4→', produce punctuation). Because of this, useHotkey("Mod+2", callback) never fires — event.key is "é", not "2".
The library already handles this correctly for AZERTY digits that map to punctuation (Mod+1, Mod+3, ...): they fall through to the event.code "Digit" fallback in matchesKeyboardEvent (match.ts). But 2/7/9 map to event.key values that are themselves single Unicode letters (é/è/ç), and the letter-key branch returns early before that fallback is ever reached:
// match.ts
const eventKey = normalizeKeyName(event.key); // "é" -> "É"
const hotkeyKey = parsed.key; // "2"
if (eventKey !== "Dead" && eventKey.length === 1 && hotkeyKey.length === 1) {
if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) return true;
if (isSingleLetterKey(eventKey) && (/^[A-Za-z]$/.test(eventKey) || !event.altKey)) return false;
// ^ isSingleLetterKey uses /^\p{Letter}$/u, which matches "É" too — this
// returns false and NEVER reaches the event.code "Digit" fallback below.
}
if (event.code && (eventKey === "Dead" || eventKey.length === 1 && hotkeyKey.length === 1)) {
if (event.code.startsWith("Digit")) { /* this never runs for É/È/Ç */ }
...
}
Steps to reproduce:
- Switch the OS keyboard layout to French (AZERTY)
- Register
useHotkey("Mod+2", callback) - Press Cmd+2 (physical digit-2 key, unshifted)
- Callback never fires —
event.keyis"é"
For comparison, useHotkey("Mod+1", callback) works, because event.key on that key is "&" (punctuation, not a Unicode letter), so it skips the early-return branch and correctly falls through to the event.code check.
All AZERTY digit keys whose unshifted glyph is a letter are affected:
| Hotkey | event.key on AZERTY |
event.code |
Matched? |
|---|---|---|---|
Mod+1 |
& |
Digit1 |
Yes |
Mod+2 |
é |
Digit2 |
No |
Mod+3 |
" |
Digit3 |
Yes |
Mod+4 |
' |
Digit4 |
Yes |
Mod+5 |
( |
Digit5 |
Yes |
Mod+6 |
- |
Digit6 |
Yes |
Mod+7 |
è |
Digit7 |
No |
Mod+8 |
_ |
Digit8 |
Yes |
Mod+9 |
ç |
Digit9 |
No |
Mod+0 |
à |
Digit0 |
No |
The fix is likely to make the letter-key early-return only fire when event.code doesn't itself resolve to a Digit*/Key* match — i.e. give the event.code fallback priority whenever it's available, rather than letting the event.key-based letter check short-circuit first. This is the same root cause as #63 (Alt+punctuation on macOS): a modifier can remap the unshifted glyph on a physical key to something that isn't the "expected" character class, and event.key-first matching keeps special-casing one remap (dead keys) without generalizing to others (AZERTY digit-row letters).
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliably reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in match.ts at matchesKeyboardEvent and trace the letter-key early return alongside the event.code Digit fallback. Reproduce the reported AZERTY cases through useHotkey("Mod+2", callback), then verify that the physical Digit2, Digit7, Digit9, and Digit0 keys match while existing punctuation and letter-key behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 74/100