static/app/components/core/hotkey/hotkey.mdx
import {Hotkey, Kbd} from '@sentry/scraps/hotkey'; import {Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; import {InlineCode} from '@sentry/scraps/code'; import {IconCommand, IconControl, IconShift, IconOption, IconReturn} from 'sentry/icons';
import * as Storybook from 'sentry/stories';
export const documentation = import('!!type-loader!@sentry/scraps/hotkey');
The <Hotkey> component displays keyboard shortcuts using semantic <kbd> elements with automatic platform-aware modifier rendering. The mod token resolves to <InlineCode><IconCommand size="xs" /></InlineCode> on macOS and Ctrl on Windows/Linux, so a single shortcut definition works everywhere. The <Kbd> component renders a single styled key cap.
<Hotkey> accepts the same key string format as the useHotkeys hook, making it easy to display the shortcuts you've already registered.
Prefer mod for the platform-primary modifier — it's the OS-agnostic shorthand for "command on Mac, ctrl elsewhere" and renders the correct glyph automatically.
<Storybook.Demo> <Flex gap="xl" align="center"> <Flex gap="sm" align="center"> <Text>Save:</Text> <Hotkey value="mod+s" /> </Flex> <Flex gap="sm" align="center"> <Text>Search:</Text> <Hotkey value="mod+k" /> </Flex> <Flex gap="sm" align="center"> <Text>Copy:</Text> <Hotkey value="mod+c" /> </Flex> </Flex> </Storybook.Demo>
<Hotkey value="mod+s" />
<Hotkey value="mod+k" />
<Hotkey value="mod+c" />
Modifiers are automatically mapped per platform. No fallback arrays needed for standard modifier differences.
| Key name | Mac | Other platforms |
|---|---|---|
mod | <InlineCode><IconCommand size="xs" /></InlineCode> | Ctrl |
command | <InlineCode><IconCommand size="xs" /></InlineCode> | Ctrl |
ctrl | <InlineCode><IconControl size="xs" /></InlineCode> | Ctrl |
alt | <InlineCode><IconOption size="xs" /></InlineCode> | Alt |
option | <InlineCode><IconOption size="xs" /></InlineCode> | Alt |
shift | <InlineCode><IconShift size="xs" /></InlineCode> | <InlineCode><IconShift size="xs" /></InlineCode> |
enter | <InlineCode><IconReturn size="xs" /></InlineCode> | <InlineCode><IconReturn size="xs" /></InlineCode> |
return | <InlineCode><IconReturn size="xs" /></InlineCode> | <InlineCode><IconReturn size="xs" /></InlineCode> |
Use command or ctrl directly only as an escape hatch for shortcuts that intentionally bind to a specific modifier on every platform (e.g. a Mac-only dev tool, or a shortcut that must be Ctrl on macOS).
Keys without an explicit glyph mapping (e.g. Home, End, PageUp) render as title-cased text.
<Storybook.Demo> <Flex gap="xl" align="center"> <Flex gap="sm" align="center"> <Text>Undo:</Text> <Hotkey value="mod+z" /> </Flex> <Flex gap="sm" align="center"> <Text>Redo:</Text> <Hotkey value="mod+shift+z" /> </Flex> <Flex gap="sm" align="center"> <Text>Navigate:</Text> <Hotkey value="shift+right" /> </Flex> </Flex> </Storybook.Demo>
<Hotkey value="mod+z" />
<Hotkey value="mod+shift+z" />
<Hotkey value="shift+right" />
Pass an array when the actual keys (not just modifiers) differ across platforms. The first combo is used. With mod, you rarely need this — only reach for it when the non-modifier keys themselves differ.
<Hotkey value={['mod+backspace', 'delete']} />
Both <Hotkey> and <Kbd> accept a variant prop. The default embossed variant renders a raised key cap; debossed renders a sunken, muted key cap suited for use on filled or dark backgrounds.
<Storybook.Demo> <Flex gap="xl" align="center"> <Flex gap="sm" align="center"> <Text>Embossed (default):</Text> <Hotkey value="mod+k" variant="embossed" /> </Flex> <Flex gap="sm" align="center"> <Text>Debossed:</Text> <Hotkey value="mod+k" variant="debossed" /> </Flex> </Flex> </Storybook.Demo>
<Hotkey value="mod+k" />
<Hotkey value="mod+k" variant="debossed" />
<Kbd>Esc</Kbd>
<Kbd variant="debossed">Esc</Kbd>
<Kbd> renders a single styled key cap. Use it for standalone key references in prose or documentation.
<Storybook.Demo> <Flex gap="lg" align="center"> <Text> Press <Kbd>Esc</Kbd> to close </Text> <Text> Use <Kbd>Tab</Kbd> to navigate </Text> <Text> Hit <Kbd>Enter</Kbd> to confirm </Text> </Flex> </Storybook.Demo>
<Text>
Press <Kbd>Esc</Kbd> to close
</Text>
<Text>
Use <Kbd>Tab</Kbd> to navigate
</Text>
The useHotkeys hook registers keyboard shortcuts. It uses the same key string format as <Hotkey>, so you can display exactly what you register.
import {Hotkey, useHotkeys} from '@sentry/scraps/hotkey';
function MyComponent() {
useHotkeys([
{match: 'mod+k', callback: () => openSearch()},
{match: 'mod+/', callback: () => openHelp()},
]);
return (
<Text>
Search: <Hotkey value="mod+k" />
</Text>
);
}
Matching is layout-aware: an AZERTY user pressing the key labeled K fires command+k, and shortcuts like command+shift+1 continue to work regardless of how Shift+1 produces a character on the user's layout.
Use enabled to gate a hotkey on state without rebuilding the array. While enabled is false the callback never fires and preventDefault is never called, so the key reaches other listeners unchanged.
useHotkeys([
{
match: 'Escape',
enabled: isPanelOpen,
callback: closePanel,
},
]);
Both <Hotkey> and <Kbd> render semantic <kbd> HTML elements. <Hotkey> uses nested <kbd> elements (an outer <kbd> wrapping inner <kbd> elements for each key), which is the correct HTML semantics for a key combination.