Files
upstream-mirror a774ac673a
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
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

125 lines
5.8 KiB
Plaintext

---
title: Locked shapes
created_at: 12/28/2025
updated_at: 12/29/2025
keywords:
- locked
- lock
- unlock
- protection
- immutable
status: published
date: 12/28/2025
readability: 9
voice: 9
completeness: 9
accuracy: 10
notes: Add links to examples. Could use more casual contractions where "doesn't" opportunities exist.
---
Locked shapes can't be selected, moved, resized, edited, or deleted through normal interactions. Lock a shape when you want it to stay exactly where it is: background elements, reference images, or layout guides that shouldn't move while you work on other parts of the canvas.
## The isLocked property
Every [shape](/docs/shapes) has an `isLocked` boolean property. When `true`, the shape is protected from most modifications:
```typescript
// Create a locked shape
editor.createShape({
type: 'geo',
x: 100,
y: 100,
isLocked: true,
props: { w: 200, h: 150, geo: 'rectangle' },
})
// Check if a shape is locked
const shape = editor.getShape(shapeId)
if (shape.isLocked) {
// Shape is locked
}
```
## Toggling lock state
Use [Editor#toggleLock](?) to flip the lock state of shapes. If shapes have mixed lock states, they all become locked:
```typescript
// Lock selected shapes
editor.toggleLock(editor.getSelectedShapeIds())
// Lock specific shapes by ID
editor.toggleLock([shapeId1, shapeId2])
```
When you lock shapes that were all previously unlocked, they automatically deselect. This prevents accidentally manipulating them while they're protected.
## What locking prevents
Locked shapes resist most operations:
| Operation | Behavior |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| Selection | Clicking a locked shape doesn't select it. Brush selection and [Editor#selectAll](?) skip locked shapes. |
| Movement | Pointer drags pass through locked shapes to the canvas. |
| Modification | [Editor#updateShape](?) ignores locked shapes, except for an update that unlocks them. |
| Deletion | [Editor#deleteShapes](?) and [Editor#duplicateShapes](?) skip locked shapes. |
| Grouping | [Editor#groupShapes](?) and [Editor#ungroupShapes](?) skip locked shapes. |
| Editing | Double-clicking a locked shape doesn't enter edit mode. |
Right-clicking a locked shape still selects it so the context menu can offer an unlock option. If you want left-click, brush, and scribble selection to include locked shapes too, set the `selectLockedShapes` option in [TldrawOptions](?). The shapes stay protected from moves, edits, and deletes; `selectAll` still skips them.
## Ancestor locking
Lock state inherits through the shape hierarchy. If a shape's ancestor is locked, the shape behaves as locked too. Check this with [Editor#isShapeOrAncestorLocked](?):
```typescript
// True if the shape or any of its parents is locked
const isProtected = editor.isShapeOrAncestorLocked(shape)
```
Locking a frame or group protects its children from pointer interactions and [Editor#updateShapes](?) without locking each shape individually. Bulk operations such as [Editor#deleteShapes](?) only check each shape's own `isLocked`, so a child of a locked frame can still be deleted programmatically.
## Bypassing locks programmatically
Sometimes you need to modify locked shapes from code: migrations, admin tools, or automated operations. Wrap your operations in [Editor#run](?) with `ignoreShapeLock: true`:
```typescript
editor.run(
() => {
// These operations affect locked shapes
editor.updateShape({ id: lockedShapeId, type: 'geo', x: 200 })
editor.deleteShapes([lockedShapeId])
},
{ ignoreShapeLock: true }
)
```
This bypasses the lock check for all operations inside the callback.
Even with `ignoreShapeLock: true`, some behaviors remain unchanged: locked shapes still can't be selected by clicking, pointer events still pass through to the canvas, and `selectAll()` still skips locked shapes. The flag affects [Editor#updateShapes](?), [Editor#deleteShapes](?), [Editor#duplicateShapes](?), [Editor#groupShapes](?), and [Editor#ungroupShapes](?), not the selection and interaction model.
## Shapes that can be edited while locked
Some shapes have interactive content that should remain usable even when locked. Embed shapes (YouTube videos, Figma files, interactive maps) are a good example: you might want the embed locked in place but still playable or navigable.
[ShapeUtils](/sdk-features/shapes#ShapeUtil) can override [ShapeUtil#canEditWhileLocked](?) (default `false`) to allow editing interactions on locked shapes. The built-in embed shape returns `true` unless its embed definition says otherwise:
```typescript
class MyInteractiveShapeUtil extends ShapeUtil<MyShape> {
override canEditWhileLocked(shape: MyShape): boolean {
return true
}
}
```
When this returns `true`, [Editor#canEditShape](?) allows the shape to enter edit mode while locked, so users can interact with its content without moving or resizing it. It doesn't make the shape hit-testable on its own: with default options a double-click on a locked shape still passes through to the canvas. The shape enters edit mode when you call [Editor#setEditingShape](?), or when the user selects it (for example with `selectLockedShapes` enabled) and presses Enter or double-clicks.
## Locking in the UI
In the default tldraw UI, users can lock shapes through the context menu or the keyboard shortcut (`Shift+L`). Right-clicking a locked shape opens the context menu with an unlock option, and the main menu has an "Unlock all" action.
## Related examples
- [Locked shapes](/examples/editor-api/locked-shapes) - Create locked shapes and modify them with `ignoreShapeLock`.