Keyboard shortcuts are essential for a fast and productive editing workflow. Plate allows you to easily define and customize shortcuts for your editor plugins.
Defining Shortcuts
You can add or modify shortcuts for any plugin when you create or configure it (e.g., using createPlatePlugin().extend({...})
or ExistingPlugin.configure({...})
). Shortcuts are defined within the shortcuts
field of your plugin configuration.
There are two primary ways to define what a shortcut does:
1. Linking to Plugin Methods (Recommended)
The most straightforward way to create a shortcut is by linking it to an existing method within your plugin. This can be either a transform method or an API method. Transforms are functions that modify the editor's state (e.g., toggling a mark, inserting an element), while API methods provide other functionality.
To do this:
- Ensure the name of your shortcut in the
shortcuts
configuration object matches the name of the method (e.g., a shortcut namedtoggle
will look for a transform namedtoggle
, or if no transform exists, an API method namedtoggle
). - Provide the
keys
(the key combination) for the shortcut.
Plate will automatically find and call the corresponding method when the specified keys are pressed.
import { createPlatePlugin, Key } from 'platejs/react';
// Example: A simplified plugin with both transforms and API
export const MyDocumentPlugin = createPlatePlugin({
key: 'doc',
})
// Define editor.tf.doc.format()
.extendTransforms(({ editor, type }) => ({
format: () => {
editor.tf.normalize({ force: true });
},
}))
// Define editor.api.doc.format()
.extendApi(({ editor, type }) => ({
save: async () => {
// Save the document
// await fetch(...);
},
}))
.extend({ // Or .configure() if extending an existing plugin
shortcuts: {
// This will call editor.tf.doc.format()
format: {
keys: [[Key.Mod, Key.Shift, 'f']], // e.g., Cmd/Ctrl + Shift + F
},
// This will call editor.api.doc.save()
save: {
keys: [[Key.Mod, 's']], // e.g., Cmd/Ctrl + S
},
},
});
The name of the shortcut (e.g., toggle
in the example) is crucial as Plate uses it to locate the matching method on the plugin. It first looks for a transform method, then falls back to an API method if no transform exists with that name.
2. Using a Custom Handler
For actions that require more complex logic, depends on the keyboard event, or if there isn't a direct one-to-one mapping with an existing transform name, you can provide a custom handler
function. This function will be executed when the shortcut is activated.
import { createPlatePlugin, Key } from 'platejs/react';
export const CustomLoggerPlugin = createPlatePlugin({
key: 'customLogger',
}).extend({
shortcuts: {
logEditorState: {
keys: [[Key.Mod, Key.Alt, 's']], // e.g., Cmd/Ctrl + Alt + S
handler: ({ editor, event, eventDetails }) => {
// 'editor' is the PlateEditor instance
// 'event' is the raw KeyboardEvent
// 'eventDetails' provides more context from the hotkey library
console.info('Current editor value:', editor.children);
console.info('Pressed keys:', eventDetails.keys);
// You might want to prevent other actions or browser defaults
// event.preventDefault();
},
},
},
});
Shortcut Configuration Properties
When defining or configuring a shortcut, you can use the following properties in its configuration object:
keys
: Required. The key combination(s) that trigger the shortcut.- This can be a string like
'mod+b'
or an array using theKey
enum for more explicit control (e.g.,[[Key.Mod, Key.Shift, 'x']]
). Key.Mod
is a convenient way to specifyCmd
on macOS andCtrl
on other operating systems.
- This can be a string like
handler
: (Optional) A function that is called when the shortcut is activated. Its signature is:({ editor: PlateEditor; event: KeyboardEvent; eventDetails: HotkeysEvent; }) => void;
If you omit thehandler
, Plate will attempt to call a matching transform based on the shortcut's name. Note: If your transform or handler returnsfalse
(e.g. not handled),preventDefault
will NOT be called, allowing other handlers or browser defaults to take over. Any other return value will use the defaultpreventDefault
behavior.preventDefault
: (Optional) A boolean. If set totrue
, it prevents the browser's default action for that key combination (e.g.,Mod+B
typically bolds text in the browser itself). Defaults totrue
. This is suitable for most editor-specific shortcuts. Set tofalse
if you need to allow the browser's default action or enable other handlers to process the event, especially if your handler might not always perform an action (e.g., an indent command that doesn't apply in the current context).priority
: (Optional) A number. If multiple plugins define shortcuts for the exact samekeys
, the shortcut with the higherpriority
number will take precedence. This is useful for resolving conflicts.- (Other options): You can also include other options compatible with the underlying
useHotkeys
hook from the@udecode/react-hotkeys
library, such asenabled
,enableOnContentEditable
, etc., to fine-tune behavior.
Default Shortcuts in Plate Plugins
Many official Plate plugins come with pre-configured shortcuts for their common actions. These defaults typically link to the plugin's internal transform methods. Currently, the following basic mark plugins include default shortcuts:
- BoldPlugin:
Mod+B
- ItalicPlugin:
Mod+I
- UnderlinePlugin:
Mod+U
Other plugins, like CodePlugin
, StrikethroughPlugin
, etc., provide transforms that can be easily linked to shortcuts (e.g., a toggle
shortcut will link to editor.tf.<pluginKey>.toggle()
), but you need to define the shortcut keys
for them explicitly.
The specific default key combinations for Bold, Italic, and Underline are defined within each plugin's default configuration. You can always override these defaults or define shortcuts for other plugins if they don't fit your needs (see "Overriding and Disabling Shortcuts" below).
Managing Multiple Shortcuts
A single plugin isn't limited to one shortcut; you can define as many as needed:
import { createPlatePlugin, Key } from 'platejs/react';
export const MyFormattingTools = createPlatePlugin({
key: 'myFormatting',
// Assuming transforms like editor.tf.myFormatting.applyHeader
// and editor.tf.myFormatting.applyCodeStyle exist.
})
.extend({
shortcuts: {
applyHeader: {
keys: [[Key.Mod, Key.Alt, '1']],
},
applyCodeStyle: {
keys: [[Key.Mod, Key.Alt, 'c']],
},
// A shortcut with a custom handler
logSomething: {
keys: [[Key.Mod, 'l']],
handler: () => console.info('Logging from MyFormattingTools!'),
},
},
});
Shortcut Priority
If multiple shortcuts (potentially from different plugins) are configured to use the exact same key combination (e.g., Mod+Shift+P
), the priority
property on the shortcut configuration object determines which shortcut's action is executed.
A higher number indicates higher priority. If priority
is not explicitly set on a shortcut, the priority
of its parent plugin is used as a fallback. This allows fine-grained control over which action takes precedence when key combinations overlap.
const PluginA = createPlatePlugin({ key: 'pluginA', priority: 10 }).extend({
shortcuts: {
doSomethingImportant: {
keys: 'mod+shift+p',
handler: () => console.info('Plugin A: Important action on Mod+Shift+P!'),
priority: 100, // Explicit, high priority for this specific shortcut
}
}
});
const PluginB = createPlatePlugin({ key: 'pluginB', priority: 20 }).extend({
shortcuts: {
doSomethingLessImportant: {
keys: 'mod+shift+p', // Same key combination as PluginA's shortcut
handler: () => console.info('Plugin B: Less important action on Mod+Shift+P.'),
// No explicit shortcut priority, will use PluginB's priority (20)
}
}
});
// If both plugins are active, pressing Mod+Shift+P will execute PluginA's handler
// for 'doSomethingImportant' because its shortcut has a higher priority (100 vs 20).
Overriding and Disabling Shortcuts
You can change or disable shortcuts for a specific plugin when you configure it.
To change a plugin's shortcut:
When you configure a plugin (e.g., BoldPlugin.configure({ ... })
), you can define a shortcut by its name (like toggle
). If the plugin already has a shortcut with that name (perhaps a default one), your new configuration for toggle
will be used for that plugin. You can change its keys
, provide a new handler
, or adjust other properties.
import { BoldPlugin, Key } from '@platejs/basic-nodes/react';
// BoldPlugin has a default shortcut named 'toggle' (typically Mod+B).
// Let's change its key combination to Mod+Shift+B for BoldPlugin.
const MyCustomBoldPlugin = BoldPlugin.configure({
shortcuts: {
toggle: { // This re-configures BoldPlugin's 'toggle' shortcut
keys: [[Key.Mod, Key.Shift, 'b']], // New key combination
// The original handler (linking to the 'toggle' transform) is often preserved
// unless a new 'handler' is specified here.
},
},
});
To disable a plugin's shortcut:
Set the shortcut's configuration to null
in that plugin's shortcuts
object. This will remove that specific shortcut (e.g., toggle
for ItalicPlugin
).
import { ItalicPlugin } from '@platejs/basic-nodes/react';
// Example: Disable the 'toggle' shortcut for the ItalicPlugin
const MyCustomItalicPlugin = ItalicPlugin.configure({
shortcuts: {
toggle: null, // This will remove/disable the ItalicPlugin's 'toggle' shortcut.
},
});
Global Shortcuts (Editor Level)
In addition to plugin-specific shortcuts, you can define global shortcuts directly on the editor instance when you create it using createPlateEditor
. These shortcuts behave similarly to plugin shortcuts.
import { createPlateEditor, Key } from 'platejs/react';
const editor = createPlateEditor({
plugins: [/* ...your array of plugins... */],
shortcuts: {
// A global shortcut, perhaps for saving the document
saveDocument: {
keys: [[Key.Mod, 's']],
handler: ({ editor, event }) => {
console.info('Attempting to save document content:', editor.children);
// Since preventDefault is set to false for this shortcut,
// the browser's save dialog will appear by default.
// If you want to conditionally prevent the default browser behavior
// (for example, only prevent saving if certain conditions are met),
// you can call event.preventDefault() inside your handler as needed:
// if (shouldPrevent) event.preventDefault();
},
preventDefault: false,
},
anotherGlobalAction: {
keys: [[Key.Ctrl, Key.Alt, 'g']],
handler: () => alert('Global action triggered!'),
}
},
});
Editor-level shortcuts generally have a high default priority but can still be influenced by the priority
settings of individual plugin shortcuts if there are conflicts.
Best Practices
- Link to Transforms: For clarity and to keep your code DRY, link shortcuts to existing transform methods by matching the shortcut name to the transform name.
preventDefault
: Most editor shortcuts should prevent the browser's default action for the key combination. Plate handles this by defaultingpreventDefault
totrue
. You generally don't need to set it explicitly. However, if your shortcut handler conditionally performs an action (e.g., an indent command that only applies if certain conditions are met), and you want other handlers or the browser's default behavior to take over if your action doesn't run, setpreventDefault: false
for that shortcut.- Maintain Consistency: Strive for intuitive and consistent key combinations. Consider standard shortcuts found in popular text editors or those that make logical sense within your application's context.
- Manage Priorities for Conflicts: If you anticipate or encounter situations where multiple plugins might try to handle the same key combination, use the
priority
property to explicitly define which shortcut should take precedence. - Provide User Feedback: For actions triggered by shortcuts that aren't immediately visible (like a "Save" action), consider providing some form of user feedback, such as a brief toast notification.