Scopes allow you to control which hotkeys are active at any given time. This is useful for modal dialogs, different views, or any scenario where you want certain hotkeys to only work in specific contexts.
Understanding Scopes
Scopes work by:
- Wrapping your app with
HotkeysProvider to manage active scopes
- Assigning hotkeys to specific scopes using the
scopes option
- Enabling/disabling scopes dynamically to control which hotkeys are active
Setup with HotkeysProvider
First, wrap your application with the HotkeysProvider:
import { HotkeysProvider } from 'react-hotkeys-hook';
function App() {
return (
<HotkeysProvider initiallyActiveScopes={['*']}>
<YourApp />
</HotkeysProvider>
);
}
The default scope is '*' (wildcard), which means all hotkeys without explicit scopes are active by default.
Assigning Hotkeys to Scopes
Use the scopes option to assign hotkeys to specific scopes:
import { useHotkeys } from 'react-hotkeys-hook';
function Editor() {
// Only active when 'editor' scope is enabled
useHotkeys('ctrl+s', () => {
console.log('Save in editor');
}, { scopes: 'editor' });
// Only active when 'viewer' scope is enabled
useHotkeys('space', () => {
console.log('Play/pause in viewer');
}, { scopes: 'viewer' });
return <div>...</div>;
}
Multiple Scopes
A hotkey can belong to multiple scopes:
useHotkeys('esc', () => {
console.log('Close dialog');
}, { scopes: ['dialog', 'modal', 'overlay'] });
The hotkey will be active if any of the specified scopes are enabled.
Managing Active Scopes
Use the useHotkeysContext hook to control which scopes are active:
import { useHotkeys, useHotkeysContext } from 'react-hotkeys-hook';
function NavigationPanel() {
const { activeScopes, enableScope, disableScope, toggleScope } = useHotkeysContext();
return (
<div>
<button onClick={() => enableScope('editor')}>
Enable Editor
</button>
<button onClick={() => disableScope('editor')}>
Disable Editor
</button>
<button onClick={() => toggleScope('viewer')}>
Toggle Viewer
</button>
<p>Active scopes: {activeScopes.join(', ')}</p>
</div>
);
}
Available Scope Functions
enableScope(scope) - Activates a scope, making all hotkeys in that scope active
disableScope(scope) - Deactivates a scope, disabling all hotkeys in that scope
toggleScope(scope) - Toggles a scope between active and inactive
activeScopes - Array of currently active scope names
Practical Example: Modal Dialog
Here’s a complete example showing how to use scopes with a modal:
import { useHotkeys, useHotkeysContext, HotkeysProvider } from 'react-hotkeys-hook';
import { useState } from 'react';
function App() {
return (
<HotkeysProvider initiallyActiveScopes={['app']}>
<MainApp />
</HotkeysProvider>
);
}
function MainApp() {
const [isModalOpen, setIsModalOpen] = useState(false);
const { enableScope, disableScope } = useHotkeysContext();
// App-level hotkey
useHotkeys('ctrl+k', () => {
setIsModalOpen(true);
enableScope('modal');
disableScope('app');
}, { scopes: 'app' });
const closeModal = () => {
setIsModalOpen(false);
disableScope('modal');
enableScope('app');
};
return (
<div>
<h1>Main App</h1>
<p>Press Ctrl+K to open modal</p>
{isModalOpen && <Modal onClose={closeModal} />}
</div>
);
}
function Modal({ onClose }) {
// Modal-specific hotkeys
useHotkeys('esc', onClose, { scopes: 'modal' });
useHotkeys('enter', () => {
console.log('Submit modal');
onClose();
}, { scopes: 'modal' });
return (
<div className="modal">
<h2>Modal Dialog</h2>
<p>Press ESC to close or Enter to submit</p>
</div>
);
}
Scope Priority
Wildcard Scope Behavior
When the wildcard scope '*' is active, all hotkeys without explicit scopes work. Once you enable a specific scope, the wildcard is automatically removed.
Multiple Active Scopes
Multiple scopes can be active simultaneously. Hotkeys will trigger if they belong to any active scope.
Scope Takes Precedence
The scopes option takes precedence over the enabled option. Even if enabled: true, a hotkey won’t trigger if its scope is not active.
function Example() {
// This will NOT work if 'editor' scope is not active,
// even though enabled is true
useHotkeys('ctrl+s', () => {
console.log('Save');
}, {
scopes: 'editor',
enabled: true // Scope takes precedence
});
}
Without HotkeysProvider
If you try to use scopes without wrapping your app in HotkeysProvider, the hotkeys will not work. The provider is required for scope management.
// ❌ This won't work - no provider
function Component() {
useHotkeys('a', () => console.log('A'), { scopes: 'test' });
return <div>...</div>;
}
// ✅ This works - has provider
function App() {
return (
<HotkeysProvider>
<Component />
</HotkeysProvider>
);
}
Advanced: View-Based Scopes
Manage hotkeys across different views in your application:
import { useHotkeys, useHotkeysContext } from 'react-hotkeys-hook';
import { useEffect } from 'react';
function EditorView() {
const { enableScope, disableScope } = useHotkeysContext();
// Activate editor scope when component mounts
useEffect(() => {
enableScope('editor');
return () => disableScope('editor');
}, []);
// Editor-specific hotkeys
useHotkeys('ctrl+b', () => console.log('Bold'), { scopes: 'editor' });
useHotkeys('ctrl+i', () => console.log('Italic'), { scopes: 'editor' });
useHotkeys('ctrl+u', () => console.log('Underline'), { scopes: 'editor' });
return <div>Editor View</div>;
}
function ViewerView() {
const { enableScope, disableScope } = useHotkeysContext();
useEffect(() => {
enableScope('viewer');
return () => disableScope('viewer');
}, []);
// Viewer-specific hotkeys
useHotkeys('space', () => console.log('Play/Pause'), { scopes: 'viewer' });
useHotkeys('left', () => console.log('Rewind'), { scopes: 'viewer' });
useHotkeys('right', () => console.log('Forward'), { scopes: 'viewer' });
return <div>Viewer View</div>;
}
Debugging Active Scopes
You can display the currently active scopes to help debug your scope configuration:
import { useHotkeysContext } from 'react-hotkeys-hook';
function ScopeDebugger() {
const { activeScopes } = useHotkeysContext();
return (
<div style={{ position: 'fixed', bottom: 10, right: 10, background: '#333', color: '#fff', padding: '10px' }}>
<strong>Active Scopes:</strong>
<ul>
{activeScopes.map(scope => (
<li key={scope}>{scope}</li>
))}
</ul>
</div>
);
}
Best Practices
- Use descriptive scope names like
'editor', 'modal', 'settings' rather than generic names
- Clean up scopes when components unmount to prevent memory leaks
- Use the wildcard scope
'*' for global hotkeys that should always work
- Consider using
useEffect to automatically manage scope lifecycle with component mounting/unmounting