diff --git a/.changeset/tangy-mangos-clap.md b/.changeset/tangy-mangos-clap.md
new file mode 100644
index 00000000..c9832260
--- /dev/null
+++ b/.changeset/tangy-mangos-clap.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/hotkeys': patch
+---
+
+`formatForDisplay` and `formatWithLabels` now order macOS modifier keys according to Apple's Human Interface Guidelines: Control → Option → Shift → Command. For example, `Mod+Shift+S` on macOS now renders as `⇧ ⌘ S` instead of `⌘ ⇧ S`. Windows and Linux output is unchanged.
diff --git a/docs/framework/lit/guides/formatting-display.md b/docs/framework/lit/guides/formatting-display.md
index 825cd66d..bb1d5045 100644
--- a/docs/framework/lit/guides/formatting-display.md
+++ b/docs/framework/lit/guides/formatting-display.md
@@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/lit-hotkeys'
// On macOS (symbols separated by spaces):
formatForDisplay('Mod+S') // "⌘ S"
-formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z"
+formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z"
formatForDisplay('Control+Alt+D') // "⌃ ⌥ D"
// On Windows/Linux:
@@ -32,7 +32,7 @@ formatForDisplay('Mod+S', {
})
```
-On macOS, modifier **order** matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
+On macOS, modifier **order** follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
`platform` is used for both normalization and display. If you need to show the same [`ParsedHotkey`](../../../reference/interfaces/ParsedHotkey.md) under several platforms, first serialize with the platform it was parsed with, then format for each display platform:
@@ -57,14 +57,14 @@ import { formatWithLabels } from '@tanstack/lit-hotkeys'
// On macOS:
formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S"
-formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z"
+formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z"
// On Windows/Linux:
formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S"
formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z"
```
-Modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key).
+On Windows and Linux, modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). On macOS, modifiers follow Apple's Human Interface Guidelines order.
## Using Formatted Hotkeys in Lit
@@ -87,7 +87,7 @@ class ShortcutBadge extends LitElement {
Usage:
```html
-
+
```
### Menu Items with Hotkeys
@@ -180,14 +180,24 @@ class CommandPaletteItem extends LitElement {
## Platform Symbols Reference
-On macOS, modifiers are displayed as symbols:
+On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards):
+
+| Modifier | Mac Symbol |
+|----------|-----------|
+| Control | `⌃` |
+| Option | `⌥` |
+| Shift | `⇧` |
+| Meta (Cmd) | `⌘` |
+
+On Windows and Linux, modifiers are displayed as text labels:
+
+| Modifier | Windows Label | Linux Label |
+|----------|---------------|-------------|
+| Meta | `Win` | `Super` |
+| Control | `Ctrl` | `Ctrl` |
+| Alt/Option | `Alt` | `Alt` |
+| Shift | `Shift` | `Shift` |
-| Modifier | Mac Symbol | Windows/Linux Label |
-|----------|-----------|-------------------|
-| Meta (Cmd) | `⌘` | `Win` / `Super` |
-| Control | `⌃` | `Ctrl` |
-| Alt/Option | `⌥` | `Alt` |
-| Shift | `⇧` | `Shift` |
Special keys also have display symbols:
diff --git a/docs/framework/preact/guides/formatting-display.md b/docs/framework/preact/guides/formatting-display.md
index f5da6630..7e2cfaa3 100644
--- a/docs/framework/preact/guides/formatting-display.md
+++ b/docs/framework/preact/guides/formatting-display.md
@@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/preact-hotkeys'
// On macOS (symbols separated by spaces):
formatForDisplay('Mod+S') // "⌘ S"
-formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z"
+formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z"
formatForDisplay('Control+Alt+D') // "⌃ ⌥ D"
// On Windows/Linux:
@@ -32,7 +32,7 @@ formatForDisplay('Mod+S', {
})
```
-On macOS, modifier order matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
+On macOS, modifier order follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
## `formatWithLabels`
@@ -43,14 +43,14 @@ import { formatWithLabels } from '@tanstack/preact-hotkeys'
// On macOS:
formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S"
-formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z"
+formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z"
// On Windows/Linux:
formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S"
formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z"
```
-Modifier order matches canonical normalization from the core package.
+On Windows and Linux, modifier order matches canonical normalization from the core package. On macOS, modifiers follow Apple's Human Interface Guidelines order.
## Using Formatted Hotkeys in Preact
@@ -65,7 +65,7 @@ function ShortcutBadge({ hotkey }: { hotkey: string }) {
// Usage
// Renders: ⌘ S (Mac) or Ctrl+S (Windows)
- // Renders: ⌘ ⇧ P (Mac) or Ctrl+Shift+P (Windows)
+ // Renders: ⇧ ⌘ P (Mac) or Ctrl+Shift+P (Windows)
```
### Menu Items with Hotkeys
@@ -125,14 +125,23 @@ function CommandPaletteItem({ command }: { command: Command }) {
## Platform Symbols Reference
-On macOS, modifiers are displayed as symbols:
+On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards):
-| Modifier | Mac Symbol | Windows/Linux Label |
-|----------|-----------|-------------------|
-| Meta (Cmd) | `⌘` | `Win` / `Super` |
-| Control | `⌃` | `Ctrl` |
-| Alt/Option | `⌥` | `Alt` |
-| Shift | `⇧` | `Shift` |
+| Modifier | Mac Symbol |
+|----------|-----------|
+| Control | `⌃` |
+| Option | `⌥` |
+| Shift | `⇧` |
+| Meta (Cmd) | `⌘` |
+
+On Windows and Linux, modifiers are displayed as text labels:
+
+| Modifier | Windows Label | Linux Label |
+|----------|---------------|-------------|
+| Meta | `Win` | `Super` |
+| Control | `Ctrl` | `Ctrl` |
+| Alt/Option | `Alt` | `Alt` |
+| Shift | `Shift` | `Shift` |
Special keys also have display symbols:
diff --git a/docs/framework/react/guides/formatting-display.md b/docs/framework/react/guides/formatting-display.md
index 1772526d..fdeb1230 100644
--- a/docs/framework/react/guides/formatting-display.md
+++ b/docs/framework/react/guides/formatting-display.md
@@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/react-hotkeys'
// On macOS (symbols separated by spaces):
formatForDisplay('Mod+S') // "⌘ S"
-formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z"
+formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z"
formatForDisplay('Control+Alt+D') // "⌃ ⌥ D"
// On Windows/Linux:
@@ -32,7 +32,7 @@ formatForDisplay('Mod+S', {
})
```
-On macOS, modifier **order** matches canonical normalization (same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⌘ ⇧ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
+On macOS, modifier **order** follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), and symbols are joined with **spaces** (e.g., `⇧ ⌘ Z`). On Windows and Linux, modifiers are joined with `+` (e.g., `Ctrl+Shift+Z`).
`platform` is used for both normalization and display. If you need to show the same [`ParsedHotkey`](../../../reference/interfaces/ParsedHotkey.md) under several platforms, first serialize with the platform it was parsed with, then format for each display platform:
@@ -57,14 +57,14 @@ import { formatWithLabels } from '@tanstack/react-hotkeys'
// On macOS:
formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S"
-formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z"
+formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z"
// On Windows/Linux:
formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S"
formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z"
```
-Modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key).
+On Windows and Linux, modifier order matches canonical normalization from the core package (e.g. `Mod` first, then `Shift`, then the key). On macOS, modifiers follow Apple's Human Interface Guidelines order.
## Using Formatted Hotkeys in React
@@ -79,7 +79,7 @@ function ShortcutBadge({ hotkey }: { hotkey: string }) {
// Usage
// Renders: ⌘ S (Mac) or Ctrl+S (Windows)
- // Renders: ⌘ ⇧ P (Mac) or Ctrl+Shift+P (Windows)
+ // Renders: ⇧ ⌘ P (Mac) or Ctrl+Shift+P (Windows)
```
### Menu Items with Hotkeys
@@ -139,14 +139,23 @@ function CommandPaletteItem({ command }: { command: Command }) {
## Platform Symbols Reference
-On macOS, modifiers are displayed as symbols:
+On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards):
-| Modifier | Mac Symbol | Windows/Linux Label |
-|----------|-----------|-------------------|
-| Meta (Cmd) | `⌘` | `Win` / `Super` |
-| Control | `⌃` | `Ctrl` |
-| Alt/Option | `⌥` | `Alt` |
-| Shift | `⇧` | `Shift` |
+| Modifier | Mac Symbol |
+|----------|-----------|
+| Control | `⌃` |
+| Option | `⌥` |
+| Shift | `⇧` |
+| Meta (Cmd) | `⌘` |
+
+On Windows and Linux, modifiers are displayed as text labels:
+
+| Modifier | Windows Label | Linux Label |
+|----------|---------------|-------------|
+| Meta | `Win` | `Super` |
+| Control | `Ctrl` | `Ctrl` |
+| Alt/Option | `Alt` | `Alt` |
+| Shift | `Shift` | `Shift` |
Special keys also have display symbols:
diff --git a/docs/framework/solid/guides/formatting-display.md b/docs/framework/solid/guides/formatting-display.md
index 63f77b6c..cc73cc2d 100644
--- a/docs/framework/solid/guides/formatting-display.md
+++ b/docs/framework/solid/guides/formatting-display.md
@@ -14,7 +14,7 @@ import { formatForDisplay } from '@tanstack/solid-hotkeys'
// On macOS (symbols separated by spaces):
formatForDisplay('Mod+S') // "⌘ S"
-formatForDisplay('Mod+Shift+Z') // "⌘ ⇧ Z"
+formatForDisplay('Mod+Shift+Z') // "⇧ ⌘ Z"
formatForDisplay('Control+Alt+D') // "⌃ ⌥ D"
// On Windows/Linux:
@@ -32,7 +32,7 @@ formatForDisplay('Mod+S', {
})
```
-On macOS, modifier order matches canonical normalization (same as `formatWithLabels`), with **spaces** between symbol segments.
+On macOS, modifier order follows [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards) (Control → Option → Shift → Command, same as `formatWithLabels`), with **spaces** between symbol segments.
## `formatWithLabels`
@@ -43,7 +43,7 @@ import { formatWithLabels } from '@tanstack/solid-hotkeys'
formatWithLabels('Mod+S', { platform: 'mac' }) // "Cmd+S"
formatWithLabels('Mod+S', { platform: 'windows' }) // "Ctrl+S"
-formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Cmd+Shift+Z"
+formatWithLabels('Mod+Shift+Z', { platform: 'mac' }) // "Shift+Cmd+Z"
formatWithLabels('Mod+Shift+Z', { platform: 'windows' }) // "Ctrl+Shift+Z"
```
@@ -107,12 +107,23 @@ function CommandPaletteItem(props: { command: Command }) {
## Platform Symbols Reference
-| Modifier | Mac Symbol | Windows/Linux Label |
-|----------|-----------|-------------------|
-| Meta (Cmd) | `⌘` | `Win` / `Super` |
-| Control | `⌃` | `Ctrl` |
-| Alt/Option | `⌥` | `Alt` |
-| Shift | `⇧` | `Shift` |
+On macOS, modifiers are displayed as symbols, and ordered per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards):
+
+| Modifier | Mac Symbol |
+|----------|-----------|
+| Control | `⌃` |
+| Option | `⌥` |
+| Shift | `⇧` |
+| Meta (Cmd) | `⌘` |
+
+On Windows and Linux, modifiers are displayed as text labels:
+
+| Modifier | Windows Label | Linux Label |
+|----------|---------------|-------------|
+| Meta | `Win` | `Super` |
+| Control | `Ctrl` | `Ctrl` |
+| Alt/Option | `Alt` | `Alt` |
+| Shift | `Shift` | `Shift` |
## Parsing and Normalization
diff --git a/docs/reference/functions/formatForDisplay.md b/docs/reference/functions/formatForDisplay.md
index 2f6ed013..f9d00d78 100644
--- a/docs/reference/functions/formatForDisplay.md
+++ b/docs/reference/functions/formatForDisplay.md
@@ -9,11 +9,12 @@ title: formatForDisplay
function formatForDisplay(hotkey, options): string;
```
-Defined in: [format.ts:92](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L92)
+Defined in: [format.ts:93](https://github.com/TanStack/hotkeys/blob/main/packages/hotkeys/src/format.ts#L93)
Formats a hotkey for display in a user interface.
-On macOS, uses symbols (⌘⇧S) in the same modifier order as [normalizeHotkeyFromParsed](normalizeHotkeyFromParsed.md).
+On macOS, uses symbols (⇧⌘S) in modifier order per [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards):
+Control → Option → Shift → Command.
On Windows/Linux, uses text (Ctrl+Shift+S) with `+` separators.
The separator can be customized with `separatorToken`.
@@ -41,7 +42,7 @@ A formatted string suitable for display
```ts
formatForDisplay('Mod+Shift+S', { platform: 'mac' })
-// Returns: '⌘ ⇧ S' (symbols separated by spaces on macOS)
+// Returns: '⇧ ⌘ S' (symbols separated by spaces on macOS)
formatForDisplay('Mod+Shift+S', { platform: 'windows' })
// Returns: 'Ctrl+Shift+S'
diff --git a/packages/hotkeys/src/format.ts b/packages/hotkeys/src/format.ts
index 1ba03b90..f7eb7cd2 100644
--- a/packages/hotkeys/src/format.ts
+++ b/packages/hotkeys/src/format.ts
@@ -8,6 +8,7 @@ import {
PUNCTUATION_KEY_DISPLAY_LABELS,
WINDOWS_MODIFIER_LABELS,
detectPlatform,
+ resolveModifier,
} from './constants'
import {
isModifierKey,
@@ -101,7 +102,11 @@ export function formatForDisplay(
hotkey as RegisterableHotkey,
platform,
)
- return normalizedHotkey
+
+ const formattedHotkey =
+ platform === 'mac' ? orderMacModifiers(normalizedHotkey) : normalizedHotkey
+
+ return formattedHotkey
.split('+')
.map((segment) => {
if (isModifierKey(segment)) {
@@ -131,6 +136,27 @@ export function formatForDisplay(
.join(separatorToken)
}
+function orderMacModifiers(hotkey: string) {
+ const modifiers: Array = []
+ const keys: Array = []
+ hotkey.split('+').forEach((segment) => {
+ if (isModifierKey(segment)) {
+ const modifier =
+ segment === 'Mod' ? resolveModifier(segment, 'mac') : segment
+ modifiers.push(modifier)
+ } else {
+ keys.push(segment)
+ }
+ })
+
+ modifiers.sort(
+ (a, b) =>
+ MODIFIER_ORDER.indexOf(a as CanonicalModifier) -
+ MODIFIER_ORDER.indexOf(b as CanonicalModifier),
+ )
+ return modifiers.concat(keys).join('+')
+}
+
/**
* @deprecated Use {@link formatForDisplay} instead with `useSymbols: false` option.
*/
diff --git a/packages/hotkeys/tests/format.test.ts b/packages/hotkeys/tests/format.test.ts
index dc763f5c..bc531772 100644
--- a/packages/hotkeys/tests/format.test.ts
+++ b/packages/hotkeys/tests/format.test.ts
@@ -98,7 +98,7 @@ describe('formatForDisplay', () => {
'⌃ ⇧ A',
)
expect(formatForDisplay(hk('Command+Shift+S'), { platform: 'mac' })).toBe(
- '⌘ ⇧ S',
+ '⇧ ⌘ S',
)
})
@@ -108,24 +108,24 @@ describe('formatForDisplay', () => {
platform: 'mac',
separatorToken: '',
}),
- ).toBe('⌘⇧S')
+ ).toBe('⇧⌘S')
expect(
formatForDisplay('Mod+Shift+S', {
platform: 'mac',
separatorToken: ' + ',
}),
- ).toBe('⌘ + ⇧ + S')
+ ).toBe('⇧ + ⌘ + S')
expect(
formatForDisplay('Mod+Shift+S', {
platform: 'mac',
separatorToken: null,
}),
- ).toBe('⌘ ⇧ S')
+ ).toBe('⇧ ⌘ S')
})
it('should resolve Mod to Command symbol', () => {
expect(formatForDisplay('Mod+S', { platform: 'mac' })).toBe('⌘ S')
- expect(formatForDisplay('Mod+Shift+S', { platform: 'mac' })).toBe('⌘ ⇧ S')
+ expect(formatForDisplay('Mod+Shift+S', { platform: 'mac' })).toBe('⇧ ⌘ S')
})
it('should use symbols for special keys', () => {
@@ -195,10 +195,10 @@ describe('formatForDisplay', () => {
meta: true,
modifiers: ['Shift', 'Meta'],
}
- expect(formatForDisplay(parsed, { platform: 'mac' })).toBe('⌘ ⇧ S')
+ expect(formatForDisplay(parsed, { platform: 'mac' })).toBe('⇧ ⌘ S')
expect(
formatForDisplay(parsed, { platform: 'mac', useSymbols: false }),
- ).toBe('Cmd+Shift+S')
+ ).toBe('Shift+Cmd+S')
})
})
@@ -248,16 +248,25 @@ describe('formatForDisplay', () => {
it('should handle multiple modifiers in canonical order (Mod first)', () => {
expect(
formatForDisplay('Mod+Shift+S', {
- platform: 'mac',
+ platform: 'windows',
useSymbols: false,
}),
- ).toBe('Cmd+Shift+S')
+ ).toBe('Ctrl+Shift+S')
+ })
+ it('when platform is mac, handling multiple modifiers should follow apple human interface guidelines', () => {
+ // Control → Option → Shift → Command
expect(
- formatForDisplay('Mod+Shift+S', {
- platform: 'windows',
+ formatForDisplay('Meta+Shift+Alt+Control+A', {
+ platform: 'mac',
useSymbols: false,
}),
- ).toBe('Ctrl+Shift+S')
+ ).toBe('Control+Option+Shift+Cmd+A')
+ expect(
+ formatForDisplay('Meta+Shift+Alt+Control+A', {
+ platform: 'mac',
+ useSymbols: true,
+ }),
+ ).toBe('⌃ ⌥ ⇧ ⌘ A')
})
})
})