# Hotkeys

URL: https://ark-ui.com/docs/utilities/hotkeys
LLM: https://ark-ui.com/llms.txt/utilities/hotkeys

Register keyboard shortcuts, sequences and scopes, and read them back to build a command palette.

---

## Setup

Register a shortcut with `useHotkey`. It listens on the document and cleans up on unmount.

```tsx
import { useFormatHotkey, useHotkey } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import styles from 'styles/hotkeys.module.css'

export const Basic = () => {
  const [count, setCount] = useState(0)
  const formatHotkey = useFormatHotkey()

  useHotkey('mod+K', () => setCount((value) => value + 1))

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>
        Press <kbd className={styles.Kbd}>{formatHotkey('mod+K')}</kbd> anywhere on this page
      </p>
      <div className={styles.Metric}>
        <span className={styles.MetricValue}>{count}</span>
        <span className={styles.MetricLabel}>{count === 1 ? 'time' : 'times'}</span>
      </div>
    </div>
  )
}
```

`mod` resolves to Command on macOS and Control elsewhere, so you write the binding once.

## Examples

### Multiple shortcuts

`useHotkeys` takes an array, which is what you want when the list comes from data. Each command needs an `id`,
a `hotkey` and an `action`.

```tsx
import { useFormatHotkey, useHotkeys, usePlatform } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import styles from 'styles/hotkeys.module.css'

const commands = [
  { id: '02-save', hotkey: 'mod+S', label: 'Save', category: 'File' },
  { id: '02-undo', hotkey: 'mod+Z', label: 'Undo', category: 'Edit' },
  { id: '02-redo', hotkey: 'mod+shift+Z', label: 'Redo', category: 'Edit' },
]

export const Multiple = () => {
  const [lastFired, setLastFired] = useState<string | null>(null)
  const platform = usePlatform()
  const formatHotkey = useFormatHotkey()

  useHotkeys(commands.map((command) => ({ ...command, action: () => setLastFired(command.id) })))

  return (
    <div className={styles.Panel}>
      <div className={styles.Toolbar}>
        <span className={styles.SectionLabel}>Detected platform</span>
        <span className={styles.Badge}>{platform}</span>
      </div>
      <ul className={styles.List}>
        {commands.map((command) => (
          <li className={styles.Row} key={command.id} data-fired={command.id === lastFired ? '' : undefined}>
            <span>{command.label}</span>
            <kbd className={styles.Kbd} data-active={command.id === lastFired ? '' : undefined}>
              {formatHotkey(command.hotkey)}
            </kbd>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

### Sequences

Press one key, then another. `G > H` fires only if both land inside the sequence window.

```tsx
import { useHotkeys } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import styles from 'styles/hotkeys.module.css'

const routes = [
  { id: '03-home', hotkey: 'G > H', keys: ['G', 'H'], label: 'Home' },
  { id: '03-settings', hotkey: 'G > S', keys: ['G', 'S'], label: 'Settings' },
]

export const Sequence = () => {
  const [page, setPage] = useState('home')

  useHotkeys(routes.map((route) => ({ id: route.id, hotkey: route.hotkey, action: () => setPage(route.id) })))

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>Press the keys in order, one after the other</p>
      <ul className={styles.List}>
        {routes.map((route) => (
          <li className={styles.Row} key={route.id} data-fired={route.id === page ? '' : undefined}>
            <span>{route.label}</span>
            <span className={styles.KeyStrip}>
              {route.keys.map((key, index) => (
                <kbd className={styles.Kbd} key={key} data-active={route.id === page ? '' : undefined}>
                  {index > 0 ? `then ${key}` : key}
                </kbd>
              ))}
            </span>
          </li>
        ))}
      </ul>
      <div className={styles.Metric}>
        <span className={styles.MetricLabel}>Current page</span>
        <span className={styles.Badge} data-state="active">
          {page}
        </span>
      </div>
    </div>
  )
}
```

### Sequence timeout

`sequenceTimeoutMs` sets that window, which defaults to one second. Wait longer and the sequence resets without
firing.

```tsx
import { HotkeysProvider, useHotkey } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import styles from 'styles/hotkeys.module.css'

const TIMEOUT_MS = 600

const Demo = () => {
  const [completed, setCompleted] = useState(0)

  useHotkey('G > H', () => setCompleted((value) => value + 1))

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>
        Press <kbd className={styles.Kbd}>G</kbd> then <kbd className={styles.Kbd}>H</kbd>. The second key must land
        within {TIMEOUT_MS}ms, otherwise the sequence resets and nothing fires.
      </p>

      <div className={styles.Metric}>
        <span className={styles.MetricValue}>{completed}</span>
        <span className={styles.MetricLabel}>{completed === 1 ? 'completion' : 'completions'}</span>
      </div>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>sequenceTimeoutMs</span>
        <span className={styles.Badge}>{TIMEOUT_MS}ms</span>
      </div>
    </div>
  )
}

export const SequenceTimeout = () => (
  <HotkeysProvider sequenceTimeoutMs={TIMEOUT_MS}>
    <Demo />
  </HotkeysProvider>
)
```

### Scopes

A command with `scopes: ['editor']` only fires while that scope is active. `store.setScope` swaps an entire set of
shortcuts at once.

```tsx
import { HotkeysProvider, useFormatHotkey, useHotkeys, useHotkeyStore } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/hotkeys.module.css'

const commands = [
  { id: '05-bold', hotkey: 'mod+B', label: 'Bold', scope: 'editor' },
  { id: '05-print', hotkey: 'mod+P', label: 'Print', scope: 'reader' },
]

const ScopeDemo = () => {
  const store = useHotkeyStore()
  const formatHotkey = useFormatHotkey()
  const [scope, setScope] = useState('editor')
  const [fired, setFired] = useState<string | null>(null)

  useHotkeys(
    commands.map((command) => ({
      id: command.id,
      hotkey: command.hotkey,
      scopes: [command.scope],
      action: () => setFired(command.id),
    })),
  )

  const toggle = () => {
    const next = scope === 'editor' ? 'reader' : 'editor'
    setScope(next)
    setFired(null)
    store.setScope(next)
  }

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>Only commands in the active scope respond</p>

      <div className={styles.Toolbar}>
        <button type="button" className={button.Root} onClick={toggle}>
          Switch scope
        </button>
        <span className={styles.Badge} data-state="active">
          {scope}
        </span>
      </div>

      <ul className={styles.List}>
        {commands.map((command) => {
          const active = command.scope === scope
          return (
            <li className={styles.Row} key={command.id} data-fired={command.id === fired ? '' : undefined}>
              <span style={{ opacity: active ? 1 : 0.45 }}>
                {command.label} <span className={styles.MetricLabel}>· {command.scope}</span>
              </span>
              <kbd className={styles.Kbd} data-active={command.id === fired ? '' : undefined}>
                {formatHotkey(command.hotkey)}
              </kbd>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export const Scopes = () => (
  <HotkeysProvider activeScopes={['editor']}>
    <ScopeDemo />
  </HotkeysProvider>
)
```

### Form fields

Single keys are ignored while you type in an input, textarea or select. Shortcuts with a modifier still fire, because
`Cmd+S` in a text field still means save. Opt a single key back in with `options: { enableOnFormTags: true }`.

```tsx
import { useFormatHotkey, useHotkeys } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import styles from 'styles/hotkeys.module.css'

export const FormFields = () => {
  const [log, setLog] = useState<string | null>(null)
  const formatHotkey = useFormatHotkey()

  useHotkeys([
    { id: '06-search', hotkey: 'S', action: () => setLog('Search (single key)') },
    { id: '06-save', hotkey: 'mod+S', action: () => setLog('Save (modifier)') },
    {
      id: '06-preview',
      hotkey: 'P',
      action: () => setLog('Preview (opted in)'),
      options: { enableOnFormTags: true },
    },
  ])

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>Try each shortcut outside the field, then again with the field focused</p>

      <input className={styles.Input} placeholder="Type here…" aria-label="Note" />

      <ul className={styles.List}>
        <li className={styles.Row}>
          <span>
            Search <span className={styles.MetricLabel}>· ignored while typing</span>
          </span>
          <kbd className={styles.Kbd}>{formatHotkey('S')}</kbd>
        </li>
        <li className={styles.Row}>
          <span>
            Save <span className={styles.MetricLabel}>· modifiers always fire</span>
          </span>
          <kbd className={styles.Kbd}>{formatHotkey('mod+S')}</kbd>
        </li>
        <li className={styles.Row}>
          <span>
            Preview <span className={styles.MetricLabel}>· enableOnFormTags</span>
          </span>
          <kbd className={styles.Kbd}>{formatHotkey('P')}</kbd>
        </li>
      </ul>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Last fired</span>
        <span className={styles.Badge} data-state={log ? 'active' : undefined}>
          {log ?? 'nothing yet'}
        </span>
      </div>
    </div>
  )
}
```

### Conflicts

When two commands claim the same shortcut, `conflictBehavior` decides. `warn` is the default and keeps both, `replace`
drops the earlier one, `allow` keeps both silently, `error` refuses the second.

```tsx
import { type ConflictBehavior, HotkeysProvider, useHotkeyRegistrations, useHotkeys } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/hotkeys.module.css'

const BEHAVIORS: ConflictBehavior[] = ['warn', 'replace', 'allow']

const Demo = () => {
  const [fired, setFired] = useState<string[]>([])

  useHotkeys([
    { id: '07-first', hotkey: 'mod+K', action: () => setFired((log) => [...log, 'First']), label: 'First' },
    { id: '07-second', hotkey: 'mod+K', action: () => setFired((log) => [...log, 'Second']), label: 'Second' },
  ])

  const commands = useHotkeyRegistrations()

  return (
    <>
      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Registered on mod+K</span>
        <div className={styles.KeyStrip}>
          {commands.length === 0 ? (
            <span className={styles.Placeholder}>none</span>
          ) : (
            commands.map((command) => (
              <span className={styles.Badge} key={command.id}>
                {command.label}
              </span>
            ))
          )}
        </div>
      </div>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Fired on last press</span>
        <span className={styles.Badge} data-state={fired.length > 0 ? 'active' : undefined}>
          {fired.slice(-2).join(' + ') || 'nothing yet'}
        </span>
      </div>
    </>
  )
}

export const Conflicts = () => {
  const [behavior, setBehavior] = useState<ConflictBehavior>('warn')

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>
        Two commands claim the same shortcut. Pick how the store resolves it, then press it.
      </p>

      <div className={styles.Toolbar}>
        {BEHAVIORS.map((value) => (
          <button
            type="button"
            className={button.Root}
            key={value}
            onClick={() => setBehavior(value)}
            data-selected={value === behavior ? '' : undefined}
          >
            {value}
          </button>
        ))}
      </div>

      <HotkeysProvider key={behavior} conflictBehavior={behavior}>
        <Demo />
      </HotkeysProvider>
    </div>
  )
}
```

### Key state

`usePressedKeys` returns the keys currently held, `useIsKeyPressed` answers for one. Use these when a held key changes
what an interaction means, like Shift to extend a selection.

```tsx
import { useHotkey, useIsKeyPressed, usePressedKeys } from '@ark-ui/react/hotkeys'
import styles from 'styles/hotkeys.module.css'

export const KeyState = () => {
  useHotkey('mod+K', () => {})

  const pressedKeys = usePressedKeys()
  const isShiftPressed = useIsKeyPressed('shift')

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>Hold any key to see it tracked live</p>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Currently pressed</span>
        <div className={styles.KeyStrip}>
          {pressedKeys.length === 0 ? (
            <span className={styles.Placeholder}>nothing</span>
          ) : (
            pressedKeys.map((key) => (
              <kbd className={styles.Kbd} key={key} data-active="">
                {key}
              </kbd>
            ))
          )}
        </div>
      </div>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Shift</span>
        <span className={styles.Badge} data-state={isShiftPressed ? 'active' : undefined}>
          <span className={styles.Dot} data-pulse={isShiftPressed ? '' : undefined} />
          {isShiftPressed ? 'Precision mode' : 'Hold Shift for precision'}
        </span>
      </div>
    </div>
  )
}
```

### Recording a shortcut

`useHotkeyRecorder` captures whatever the user presses, for a "click to rebind" setting. Escape cancels, Backspace
clears, and chords and sequences both work.

```tsx
import { useHotkeyRecorder } from '@ark-ui/react/hotkeys'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/hotkeys.module.css'

export const Recorder = () => {
  const [binding, setBinding] = useState<string | null>(null)
  const [lastEvent, setLastEvent] = useState<string | null>(null)

  const recorder = useHotkeyRecorder({
    onRecord: (hotkey) => {
      setBinding(hotkey.display)
      setLastEvent('recorded')
    },
    onCancel: () => setLastEvent('cancelled'),
    onClear: () => {
      setBinding(null)
      setLastEvent('cleared')
    },
  })

  return (
    <div className={styles.Panel}>
      <p className={styles.Hint}>
        Click record, then press a shortcut. <kbd className={styles.Kbd}>Esc</kbd> cancels,{' '}
        <kbd className={styles.Kbd}>Backspace</kbd> clears.
      </p>

      <div className={styles.Toolbar}>
        <button type="button" className={button.Root} onClick={() => recorder.start()} disabled={recorder.recording}>
          {recorder.recording ? 'Listening…' : 'Record shortcut'}
        </button>
        {recorder.recording && (
          <span className={styles.Badge} data-state="active">
            <span className={styles.Dot} data-pulse="" />
            {recorder.value?.display ?? 'Press a key'}
          </span>
        )}
      </div>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Bound to</span>
        <div className={styles.KeyStrip}>
          {binding ? (
            <kbd className={styles.Kbd} data-active="">
              {binding}
            </kbd>
          ) : (
            <span className={styles.Placeholder}>nothing yet</span>
          )}
        </div>
      </div>

      <div className={styles.Section}>
        <span className={styles.SectionLabel}>Last event</span>
        <span className={styles.Badge}>{lastEvent ?? 'none'}</span>
      </div>
    </div>
  )
}
```

### Command palette

`useHotkeyRegistrations` returns every registered command with its metadata, so the palette is a view over the
registry instead of a second list you keep in sync.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { Dialog } from '@ark-ui/react/dialog'
import { useFormatHotkey, useHotkey, useHotkeyRegistrations, useHotkeys } from '@ark-ui/react/hotkeys'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CornerDownLeftIcon, SearchIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/command-palette.module.css'

export const CommandPalette = () => {
  const [open, setOpen] = useState(false)
  const [lastRun, setLastRun] = useState<string | null>(null)

  useHotkeys([
    {
      id: '10-save',
      hotkey: 'mod+S',
      action: () => setLastRun('Save file'),
      label: 'Save file',
      category: 'File',
      keywords: ['write', 'persist'],
    },
    {
      id: '10-theme',
      hotkey: 'mod+shift+D',
      action: () => setLastRun('Toggle theme'),
      label: 'Toggle theme',
      category: 'View',
      keywords: ['dark', 'light', 'appearance'],
    },
    {
      id: '10-undo',
      hotkey: 'mod+Z',
      action: () => setLastRun('Undo'),
      label: 'Undo',
      category: 'Edit',
      keywords: ['revert', 'back'],
    },
  ])

  const formatHotkey = useFormatHotkey()
  const commands = useHotkeyRegistrations()
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter, set } = useListCollection({
    initialItems: commands,
    itemToString: (item) => item.label ?? item.id,
    itemToValue: (item) => item.id,
    groupBy: (item) => item.category ?? 'Other',
    filter: (itemText, filterText, item) =>
      contains(itemText, filterText) || item.keywords.some((keyword) => contains(keyword, filterText)),
  })

  useEffect(() => {
    set([...commands])
  }, [commands, set])

  const openPalette = () => setOpen(true)

  useHotkey('mod+shift+P', openPalette, { label: 'Open command palette', category: 'General' })

  const handleValueChange = (details: Combobox.ValueChangeDetails) => {
    const selected = details.items.at(0)
    if (!selected) return
    setOpen(false)
    selected.action(new KeyboardEvent('keydown'))
  }

  return (
    <div>
      <button type="button" className={button.Root} onClick={openPalette}>
        Open palette ({formatHotkey('mod+shift+P')})
      </button>
      <p>Last run: {lastRun ?? 'nothing yet'}</p>

      <Dialog.Root
        lazyMount
        unmountOnExit
        open={open}
        onOpenChange={(details) => setOpen(details.open)}
        onExitComplete={() => filter('')}
      >
        <Portal>
          <Dialog.Backdrop className={styles.Backdrop} />
          <Dialog.Positioner className={styles.Positioner}>
            <Dialog.Content className={styles.Content} aria-label="Command palette">
              <Combobox.Root
                className={styles.Root}
                collection={collection}
                open
                disableLayer
                inputBehavior="autohighlight"
                selectionBehavior="preserve"
                loopFocus={false}
                placeholder="Search commands…"
                onInputValueChange={(details) => filter(details.inputValue)}
                onValueChange={handleValueChange}
              >
                <Combobox.Control className={styles.Control}>
                  <SearchIcon />
                  <Combobox.Input className={styles.Input} />
                </Combobox.Control>
                <Combobox.Content className={styles.Content_list}>
                  {collection.size === 0 ? (
                    <div className={styles.Empty}>No commands found</div>
                  ) : (
                    collection.group().map(([category, group]) => (
                      <Combobox.ItemGroup className={styles.ItemGroup} key={category}>
                        <Combobox.ItemGroupLabel className={styles.ItemGroupLabel}>{category}</Combobox.ItemGroupLabel>
                        {group.map((item) => (
                          <Combobox.Item className={styles.Item} key={item.id} item={item} persistFocus>
                            <Combobox.ItemText className={styles.ItemText}>{item.label ?? item.id}</Combobox.ItemText>
                            <kbd className={styles.Shortcut}>{formatHotkey(item.hotkey)}</kbd>
                          </Combobox.Item>
                        ))}
                      </Combobox.ItemGroup>
                    ))
                  )}
                </Combobox.Content>
              </Combobox.Root>
              <div className={styles.Footer}>
                <span className={styles.FooterHint}>
                  <CornerDownLeftIcon size={12} /> to run
                </span>
                <span className={styles.FooterHint}>esc to close</span>
              </div>
            </Dialog.Content>
          </Dialog.Positioner>
        </Portal>
      </Dialog.Root>
    </div>
  )
}
```

Register once with `label`, `category` and `keywords`, then group by `category`, search `keywords`, and call
`item.action` on select. Here, typing "dark" finds "Toggle theme" even though its label has no "dark" in it.

## Guides

### Displaying a shortcut

`useFormatHotkey` returns a formatter bound to the current platform, so `mod+K` renders as `⌘ K` on macOS and
`Ctrl K` elsewhere.

```tsx
const formatHotkey = useFormatHotkey()

return <kbd>{formatHotkey('mod+K')}</kbd>
```

Use `formatHotkey` from `@zag-js/hotkeys` only outside a component. Inside one it reads the platform during render,
which mismatches on hydration when the server says `Ctrl K` and the browser says `⌘ K`. For the platform itself,
`usePlatform` returns `mac`, `windows` or `linux`.

### Sharing one store

Without a provider, every hook shares a default store. Wrap your tree in `HotkeysProvider` to set defaults for every
command, control active scopes, or isolate a subtree.

```tsx
<HotkeysProvider activeScopes={['editor']} conflictBehavior="replace" sequenceTimeoutMs={800}>
  <App />
</HotkeysProvider>
```

`defaultOptions` applies to every command registered under it, and per-command `options` override it.

### Enabling and disabling

Pass `enabled` as a boolean or a function. A function is re-evaluated each time the key fires, so it reads current
state without re-registering.

```tsx
useHotkey('mod+S', save, { enabled: () => !isReadOnly })
```

### Reacting to a key release

`options: { eventType: 'keyup' }` fires on release instead of press. Pair it with a `keydown` command on the same key
for push-to-talk.

## API Reference

### HotkeysProvider

#### Props

**`activeScopes`**
Type: `string | string[]`
Required: false
Default Value: `['*']`
Description: The scopes that are active. Only commands in an active scope fire.

**`conflictBehavior`**
Type: `'warn' | 'error' | 'replace' | 'allow'`
Required: false
Default Value: `'warn'`
Description: What to do when two commands register the same hotkey. warn keeps both and logs, replace drops the earlier one, allow keeps both silently, error refuses the second.

**`defaultOptions`**
Type: `HotkeyOptions`
Required: false
Default Value: `undefined`
Description: Options applied to every command registered under this provider. Per-command options override it.

**`sequenceTimeoutMs`**
Type: `number`
Required: false
Default Value: `1000`
Description: How long a sequence like G > H waits for the next key before resetting.

### useHotkey

#### Props

**`hotkey`**
Type: `string`
Required: true
Default Value: `undefined`
Description: The key combination, such as mod+K, shift+alt+D or the sequence G > H. mod resolves to Command on macOS and Control elsewhere.

**`action`**
Type: `(event: KeyboardEvent) => void`
Required: true
Default Value: `undefined`
Description: Called when the hotkey fires.

**`options`**
Type: `UseHotkeyOptions`
Required: false
Default Value: `undefined`
Description: Everything on UseHotkeysCommand except id, hotkey and action. The id is generated for you.

### useHotkeys

#### Props

**`commands`**
Type: `UseHotkeysCommand[]`
Required: true
Default Value: `undefined`
Description: The commands to register. Each is keyed by id, so changing one re-registers only that command.

### UseHotkeysCommand

#### Props

**`id`**
Type: `string`
Required: true
Default Value: `undefined`
Description: Unique identifier. Used to reconcile registrations across renders and to unregister on unmount.

**`hotkey`**
Type: `string`
Required: true
Default Value: `undefined`
Description: The key combination or sequence that triggers the command.

**`action`**
Type: `(event: KeyboardEvent) => void`
Required: true
Default Value: `undefined`
Description: Called when the hotkey fires.

**`label`**
Type: `string`
Required: false
Default Value: `undefined`
Description: Human-readable name. Read back by useHotkeyRegistrations, so a command palette can render it.

**`description`**
Type: `string`
Required: false
Default Value: `undefined`
Description: Longer explanation of what the command does.

**`category`**
Type: `string`
Required: false
Default Value: `undefined`
Description: Group name, for sectioning a command palette.

**`keywords`**
Type: `string[]`
Required: false
Default Value: `undefined`
Description: Alternative search terms. Lets a palette match "dark" against a command labelled "Toggle theme".

**`scopes`**
Type: `string | string[]`
Required: false
Default Value: `'*'`
Description: The scopes this command belongs to. It only fires while one of them is active.

**`enabled`**
Type: `boolean | (() => boolean)`
Required: false
Default Value: `true`
Description: Whether the command can fire. A function is re-evaluated on every key press, so it reads current state without re-registering.

**`options`**
Type: `HotkeyOptions`
Required: false
Default Value: `undefined`
Description: Per-command behavior. Overrides the provider defaultOptions.

### HotkeyOptions

#### Props

**`preventDefault`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Call preventDefault() on the event before running the action.

**`stopPropagation`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Call stopPropagation() on the event before running the action.

**`enableOnFormTags`**
Type: `boolean | ('input' | 'textarea' | 'select')[]`
Required: false
Default Value: `false`
Description: Whether a single-key shortcut fires while an input, textarea or select has focus. Shortcuts with a modifier always fire. Pass an array to opt in to specific tags.

**`enableOnContentEditable`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether the shortcut fires inside a contenteditable element.

**`capture`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Listen in the capture phase.

**`requireReset`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Fire once per press. The key must be released before it fires again, which suppresses key repeat.

**`eventType`**
Type: `'keydown' | 'keyup'`
Required: false
Default Value: `'keydown'`
Description: Whether to fire on press or on release. Pair a keyup command with a keydown one on the same key for push-to-talk.

**`target`**
Type: `Element | (() => Element | null)`
Required: false
Default Value: `undefined`
Description: Scope the command to a DOM subtree. It only fires when the event originates inside this element, which must contain focus. Resolved on every event, and skipped while it resolves to null.

### useHotkeyRegistrations

#### Props

**`returns`**
Type: `HotkeyCommand[]`
Required: false
Default Value: `undefined`
Description: Every command currently registered on the store, with its label, category, keywords and resolved hotkey. Re-reads when commands are added or removed, which is what lets a command palette be a view over the registry instead of a second list to keep in sync.

### useHotkeyStore

#### Props

**`returns`**
Type: `HotkeyStore`
Required: false
Default Value: `undefined`
Description: The store from the nearest HotkeysProvider, or a shared default store if there is none. Use setScope, addScope, removeScope and toggleScope to change which commands are live, and isPressed to test a combination directly.

### usePressedKeys

#### Props

**`returns`**
Type: `string[]`
Required: false
Default Value: `undefined`
Description: The keys currently held down. Use it when a held key changes what an interaction means, such as Shift to extend a selection.

### useIsKeyPressed

#### Props

**`hotkey`**
Type: `string`
Required: true
Default Value: `undefined`
Description: The key or combination to watch.

**`returns`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether that key or combination is currently held.

### usePlatform

#### Props

**`returns`**
Type: `Platform`
Required: false
Default Value: `undefined`
Description: The current platform, one of 'mac', 'windows' or 'linux'. Resolves after mount, so server and client render the same markup.

### useFormatHotkey

#### Props

**`returns`**
Type: `(hotkey: string, options?: HotkeyFormatOptions) => string`
Required: false
Default Value: `undefined`
Description: A formatter bound to the current platform, so mod+K renders as ⌘ K on macOS and Ctrl K elsewhere. Prefer this over importing formatHotkey directly inside a component, which reads the platform during render and mismatches on hydration.

### useHotkeyRecorder

#### Props

**`props`**
Type: `UseHotkeyRecorderProps`
Required: false
Default Value: `undefined`
Description: Accepts onRecord, onCancel, onClear, formatOptions and sequenceTimeoutMs.

**`returns`**
Type: `UseHotkeyRecorderReturn`
Required: false
Default Value: `undefined`
Description: The recorder handle, described below.

### UseHotkeyRecorderReturn

#### Props

**`recording`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the recorder is currently listening for key events.

**`value`**
Type: `RecordedHotkey | null`
Required: false
Default Value: `undefined`
Description: The hotkey recorded so far. value is the raw string, display is the platform-formatted one.

**`start`**
Type: `() => void`
Required: false
Default Value: `undefined`
Description: Start listening for key events.

**`stop`**
Type: `() => void`
Required: false
Default Value: `undefined`
Description: Stop listening and keep the recorded hotkey.

**`cancel`**
Type: `() => void`
Required: false
Default Value: `undefined`
Description: Stop listening and discard the recorded hotkey. Also triggered by Escape.

**`clear`**
Type: `() => void`
Required: false
Default Value: `undefined`
Description: Clear the recorded hotkey. Also triggered by Backspace or Delete.