Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
316 lines
14 KiB
Plaintext
316 lines
14 KiB
Plaintext
---
|
|
title: Actions
|
|
created_at: 12/25/2025
|
|
updated_at: 12/29/2025
|
|
keywords:
|
|
- actions
|
|
- commands
|
|
- keyboard shortcuts
|
|
- menus
|
|
- overrides
|
|
- customization
|
|
status: published
|
|
date: 12/25/2025
|
|
readability: 8
|
|
voice: 8
|
|
completeness: 9
|
|
accuracy: 9
|
|
notes: Interface definition shows property order different from source. Helper table has slight differences from implementation details.
|
|
---
|
|
|
|
Actions are named operations that users trigger from menus, keyboard shortcuts, or custom UI. Each action bundles an identifier, display metadata (label, icon, keyboard shortcut), and a handler function. Actions let you define operations like "undo", "group", or "export as PNG" once and invoke them from multiple places with consistent behavior.
|
|
|
|
```tsx
|
|
import { Tldraw, TLUiOverrides } from 'tldraw'
|
|
import 'tldraw/tldraw.css'
|
|
|
|
const overrides: TLUiOverrides = {
|
|
actions(editor, actions, helpers) {
|
|
// Add a custom action
|
|
actions['show-selection-count'] = {
|
|
id: 'show-selection-count',
|
|
label: 'action.show-selection-count',
|
|
kbd: 'shift+c',
|
|
onSelect(source) {
|
|
const count = editor.getSelectedShapeIds().length
|
|
helpers.addToast({ title: `${count} shapes selected` })
|
|
},
|
|
}
|
|
return actions
|
|
},
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<div style={{ position: 'fixed', inset: 0 }}>
|
|
<Tldraw overrides={overrides} />
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
The `tldraw` package includes nearly 100 default actions covering editing, arrangement, export, zoom, and preferences. You can override any of these or add your own through the `overrides` prop, typed as [TLUiOverrides](?).
|
|
|
|
## How actions work
|
|
|
|
Actions live in a React context inside the tldraw UI. When the UI mounts, it registers all default actions, applies any overrides you've provided, and makes them available through the [useActions](?) hook. Menus and toolbars look up actions by ID and render them with their labels, icons, and keyboard shortcuts.
|
|
|
|
Each action has an `onSelect` handler that receives a source parameter indicating where it was triggered:
|
|
|
|
```tsx
|
|
const actions = useActions()
|
|
const duplicateAction = actions['duplicate']
|
|
|
|
// Trigger programmatically
|
|
duplicateAction.onSelect('toolbar')
|
|
```
|
|
|
|
Keyboard shortcuts are bound automatically. The [useKeyboardShortcuts](?) hook parses each action's `kbd` property and registers hotkey handlers. When a shortcut fires, it calls the action's `onSelect` with `'kbd'` as the source.
|
|
|
|
## Action structure
|
|
|
|
The [TLUiActionItem](?) interface defines what an action contains:
|
|
|
|
| Property | Description |
|
|
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `id` | Unique identifier for the action (e.g., `'duplicate'`, `'zoom-in'`) |
|
|
| `label` | Translation key for display text. Can be a string or an object mapping contexts to different keys (see [Context-sensitive labels](#context-sensitive-labels)) |
|
|
| `icon` | Icon name from tldraw's icon set, or a custom React element |
|
|
| `kbd` | Keyboard shortcut string. Use commas to bind several combinations, e.g. `'cmd+g,ctrl+g'` (see [Keyboard shortcuts](#keyboard-shortcuts)) |
|
|
| `readonlyOk` | When `true`, the action works in readonly mode. Defaults to `false` |
|
|
| `checkbox` | When `true`, renders as a toggle with a checkmark indicator in menus |
|
|
| `isRequiredA11yAction` | When `true`, the keyboard shortcut works even when shortcuts are normally disabled (e.g., while editing a shape). Used for accessibility actions |
|
|
| `onSelect` | Handler called when the action is triggered. Receives a [TLUiEventSource](?) indicating the trigger origin (`'kbd'`, `'menu'`, `'toolbar'`, etc.) |
|
|
|
|
## Accessing actions
|
|
|
|
Use the [useActions](?) hook to get all registered actions:
|
|
|
|
```typescript
|
|
import { useActions } from 'tldraw'
|
|
|
|
function MyComponent() {
|
|
const actions = useActions()
|
|
|
|
return (
|
|
<button onClick={() => actions['undo'].onSelect('toolbar')}>
|
|
Undo
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
The hook returns a record mapping action IDs to action objects. You can iterate over it to build custom menus or filter actions by property.
|
|
|
|
## Default actions
|
|
|
|
The default actions cover most editing operations you'd expect in a canvas application. Some common ones, by category:
|
|
|
|
| Category | Action ids |
|
|
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Editing | `undo`, `redo`, `duplicate`, `delete`, `copy`, `cut`, `paste` |
|
|
| Grouping | `group`, `ungroup` |
|
|
| Arrangement | `bring-to-front`, `bring-forward`, `send-backward`, `send-to-back`, `align-left`, `align-center-horizontal`, `align-right`, `distribute-horizontal`, `distribute-vertical` |
|
|
| Export | `export-as-svg`, `export-as-png`, `copy-as-svg`, `copy-as-png` |
|
|
| Zoom | `zoom-in`, `zoom-out`, `zoom-to-100`, `zoom-to-fit`, `zoom-to-selection`, `select-zoom-tool` |
|
|
| Preferences | `toggle-dark-mode`, `toggle-snap-mode`, `toggle-grid`, `toggle-focus-mode` |
|
|
|
|
The full list is in the [default actions source](https://github.com/tldraw/tldraw/blob/main/packages/tldraw/src/lib/ui/context/actions.tsx). Most actions guard themselves inside their handlers. For example, `group` and the arrangement actions do nothing unless shapes are selected and the select tool is active.
|
|
|
|
## Overriding actions
|
|
|
|
Pass an `overrides` prop to customize actions. The override function receives the editor, the default actions, and helper utilities:
|
|
|
|
```typescript
|
|
import { Tldraw, TLUiOverrides } from 'tldraw'
|
|
|
|
const overrides: TLUiOverrides = {
|
|
actions(editor, actions, helpers) {
|
|
// Modify existing action
|
|
actions['duplicate'].kbd = 'cmd+shift+d,ctrl+shift+d'
|
|
|
|
// Disable an action by removing it
|
|
delete actions['print']
|
|
|
|
return actions
|
|
},
|
|
}
|
|
|
|
function App() {
|
|
return <Tldraw overrides={overrides} />
|
|
}
|
|
```
|
|
|
|
The `copy`, `cut`, and `paste` shortcuts are handled by native clipboard events rather than the `kbd` system, so changing their `kbd` has no effect.
|
|
|
|
### Modifying behavior
|
|
|
|
To change what an action does, replace its `onSelect` handler:
|
|
|
|
```typescript
|
|
const overrides: TLUiOverrides = {
|
|
actions(editor, actions, helpers) {
|
|
const originalDuplicate = actions['duplicate'].onSelect
|
|
|
|
actions['duplicate'].onSelect = async (source) => {
|
|
console.log('Duplicating shapes...')
|
|
await originalDuplicate(source)
|
|
console.log('Done!')
|
|
}
|
|
|
|
return actions
|
|
},
|
|
}
|
|
```
|
|
|
|
You can call the original handler before or after your custom logic, or replace it entirely.
|
|
|
|
### Adding custom actions
|
|
|
|
Add new actions by inserting them into the actions record:
|
|
|
|
```typescript
|
|
const overrides: TLUiOverrides = {
|
|
actions(editor, actions, helpers) {
|
|
actions['my-custom-action'] = {
|
|
id: 'my-custom-action',
|
|
label: 'action.my-custom-action',
|
|
kbd: 'cmd+shift+k,ctrl+shift+k',
|
|
icon: 'external-link',
|
|
onSelect(source) {
|
|
const shapes = editor.getSelectedShapes()
|
|
console.log('Custom action on', shapes.length, 'shapes')
|
|
},
|
|
}
|
|
|
|
return actions
|
|
},
|
|
}
|
|
```
|
|
|
|
Custom actions integrate with the keyboard shortcut system automatically. To add them to menus, override the menu components; see [Actions in menus](#actions-in-menus) and the [custom menus example](/examples/ui/custom-menus).
|
|
|
|
### Using helper utilities
|
|
|
|
The override function receives a `helpers` object ([TLUiOverrideHelpers](?), the return value of [useDefaultHelpers](?)):
|
|
|
|
```typescript
|
|
const overrides: TLUiOverrides = {
|
|
actions(editor, actions, helpers) {
|
|
actions['show-toast'] = {
|
|
id: 'show-toast',
|
|
label: 'action.show-toast',
|
|
onSelect(source) {
|
|
helpers.addToast({
|
|
title: 'Hello!',
|
|
description: 'This is a custom action.',
|
|
})
|
|
},
|
|
}
|
|
|
|
return actions
|
|
},
|
|
}
|
|
```
|
|
|
|
Available helpers:
|
|
|
|
| Helper | Description |
|
|
| ----------------------- | ------------------------------------ |
|
|
| `addToast` | Show a toast notification |
|
|
| `removeToast` | Remove a specific toast |
|
|
| `clearToasts` | Remove all toasts |
|
|
| `addDialog` | Open a dialog |
|
|
| `removeDialog` | Close a specific dialog |
|
|
| `clearDialogs` | Close all dialogs |
|
|
| `msg` | Get a translated string by key |
|
|
| `isMobile` | Boolean indicating mobile breakpoint |
|
|
| `insertMedia` | Open file picker and insert media |
|
|
| `replaceImage` | Replace selected image with new file |
|
|
| `replaceVideo` | Replace selected video with new file |
|
|
| `printSelectionOrPages` | Print selection or all pages |
|
|
| `cut` | Cut selected shapes to clipboard |
|
|
| `copy` | Copy selected shapes to clipboard |
|
|
| `paste` | Paste from clipboard |
|
|
| `copyAs` | Copy shapes as SVG or PNG |
|
|
| `exportAs` | Export shapes as SVG, PNG, or JSON |
|
|
| `getEmbedDefinition` | Get embed info for a URL |
|
|
|
|
## Keyboard shortcuts
|
|
|
|
Shortcuts use a simple string format with modifier keys separated by `+`. Use commas to bind several combinations to the same action. Every combination is active on every platform; the conventional `cmd+…,ctrl+…` pair covers Mac and everything else, and only the shortcut hint shown in menus ([TldrawUiKbd](?)) is platform-specific:
|
|
|
|
```typescript
|
|
kbd: 'cmd+g,ctrl+g' // Cmd+G or Ctrl+G
|
|
kbd: 'shift+1' // Shift+1
|
|
kbd: 'cmd+shift+s,ctrl+shift+s' // Cmd+Shift+S or Ctrl+Shift+S
|
|
```
|
|
|
|
Modifiers are `cmd` (alias `meta`), `ctrl`, `shift`, and `alt` (alias `option`). Special keys include `del`, `backspace`, `enter`, `escape`, `space`, and the arrow keys (`left`, `right`, `up`, `down`).
|
|
|
|
To bind the `+` key itself, double the trailing plus: `cmd++`. That matches an unshifted `+` key (German, Nordic and similar layouts, or the numpad). On layouts where `+` is shift plus another key, such as US, write `cmd+shift++`, which is the same binding as `cmd+shift+=`.
|
|
|
|
Shortcuts match the character the browser reports for the key press, not the physical keycap, with two exceptions: shifted number-row keys match by position, so write them as `shift+<digit>` on every layout, and when the reported character is non-ASCII (Cyrillic, Greek, macOS Option dead keys) matching falls back to the physical key's US QWERTY character, so `cmd+z` still works on a Russian layout. With shift held, the shifted and unshifted US spellings are the same binding, so `shift+:` and `shift+;` both fire when the browser reports `:` with shift held. Bind what the keyboard emits, not what the keycap says: on French AZERTY the `:` key emits `/` with shift held, so bind `shift+/`; on Japanese JIS the `^` key emits `~`, so bind `shift+~`. The characters `!`, `?` and `$` are reserved as legacy modifier markers and cannot be used as keys.
|
|
|
|
Shortcuts only fire while the editor is focused and the key event does not target a text input. They are also disabled when a menu is open, a shape is being edited, the editor has a crashing error, or the user has disabled keyboard shortcuts in preferences. In readonly mode, only actions with `readonlyOk` are bound. Actions marked with `isRequiredA11yAction: true` bypass the disabled check for accessibility purposes.
|
|
|
|
## Actions in menus
|
|
|
|
The default UI uses [TldrawUiMenuActionItem](?) to render actions in menus:
|
|
|
|
```typescript
|
|
import { TldrawUiMenuActionItem, TldrawUiMenuGroup } from 'tldraw'
|
|
|
|
function CustomMenu() {
|
|
return (
|
|
<TldrawUiMenuGroup id="edit">
|
|
<TldrawUiMenuActionItem actionId="undo" />
|
|
<TldrawUiMenuActionItem actionId="redo" />
|
|
<TldrawUiMenuActionItem actionId="duplicate" />
|
|
</TldrawUiMenuGroup>
|
|
)
|
|
}
|
|
```
|
|
|
|
This component looks up the action by ID and renders it with the correct label, icon, and shortcut hint. Pass `disabled` yourself if the item should be disabled. For toggle actions, use [TldrawUiMenuActionCheckboxItem](?) with a `checked` prop.
|
|
|
|
## Context-sensitive labels
|
|
|
|
Some actions show different labels depending on where they appear. The `label` property can be an object mapping menu context types ([TLUiMenuContextType](?), plus `default`) to translation keys:
|
|
|
|
```typescript
|
|
actions['export-as-svg'] = {
|
|
id: 'export-as-svg',
|
|
label: {
|
|
default: 'action.export-as-svg',
|
|
menu: 'action.export-as-svg.short',
|
|
'context-menu': 'action.export-as-svg.short',
|
|
},
|
|
// ...
|
|
}
|
|
```
|
|
|
|
The menu component uses the appropriate label based on its context. If no specific label exists for a context, it falls back to `default`.
|
|
|
|
## Tracking action usage
|
|
|
|
The `source` parameter tells you where the action was triggered. Use this for analytics:
|
|
|
|
```tsx
|
|
actions['custom-action'] = {
|
|
id: 'custom-action',
|
|
label: 'action.custom',
|
|
kbd: 'cmd+k,ctrl+k',
|
|
onSelect(source) {
|
|
trackEvent('custom-action', { source })
|
|
// source: 'kbd', 'menu', 'context-menu', 'toolbar', 'quick-actions', 'zoom-menu', etc.
|
|
},
|
|
}
|
|
```
|
|
|
|
## Related examples
|
|
|
|
- [Action overrides](/examples/ui/action-overrides) - Add custom actions and modify existing action shortcuts using the overrides prop.
|
|
- [Keyboard shortcuts](/examples/ui/keyboard-shortcuts) - Change keyboard shortcuts for tools and actions.
|
|
- [Custom menus](/examples/ui/custom-menus) - Build custom menus that use actions with proper labels and shortcuts.
|