snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
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
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
This commit is contained in:
217
apps/docs/content/sdk-features/selection.mdx
Normal file
217
apps/docs/content/sdk-features/selection.mdx
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
title: Selection
|
||||
created_at: 12/20/2025
|
||||
updated_at: 12/29/2025
|
||||
keywords:
|
||||
- selection
|
||||
- select
|
||||
- bounds
|
||||
- rotation
|
||||
- group
|
||||
status: published
|
||||
date: 12/20/2025
|
||||
readability: 9
|
||||
voice: 8
|
||||
completeness: 9
|
||||
accuracy: 9
|
||||
notes: Opening paragraph is dense - split into overview and details. "Serves as the source of truth" is hollow phrasing.
|
||||
---
|
||||
|
||||
The editor tracks which shapes are selected. You can change the selection, read the selected shapes, and get their combined bounds and rotation.
|
||||
|
||||
The editor also enforces a few rules on its own: you can't select both a group and its children at the same time, and selecting shapes inside a group focuses that group.
|
||||
|
||||
## Selected shape IDs
|
||||
|
||||
The editor tracks selection through the `selectedShapeIds` array in the current page's [instance state](/sdk-features/instance-state). This array holds the IDs of all currently selected shapes. Everything else about the selection (bounds, rotation, the selected shape records) is derived from it.
|
||||
|
||||
```typescript
|
||||
// Get currently selected shape IDs
|
||||
const selectedIds = editor.getSelectedShapeIds()
|
||||
|
||||
// Get the actual shape objects
|
||||
const selectedShapes = editor.getSelectedShapes()
|
||||
```
|
||||
|
||||
[Editor#getSelectedShapes](?) resolves the IDs to shape records and drops any IDs that no longer exist in the store.
|
||||
|
||||
## Selection methods
|
||||
|
||||
### Basic selection
|
||||
|
||||
Use [Editor#select](?), [Editor#setSelectedShapes](?), [Editor#deselect](?), and [Editor#selectNone](?) to change the selection:
|
||||
|
||||
```typescript
|
||||
// Select specific shapes (replaces current selection)
|
||||
editor.select(shapeId1, shapeId2)
|
||||
editor.setSelectedShapes([shapeId1, shapeId2])
|
||||
|
||||
// Deselect specific shapes (removes from current selection)
|
||||
editor.deselect(shapeId1)
|
||||
|
||||
// Clear all selection
|
||||
editor.selectNone()
|
||||
```
|
||||
|
||||
Both `select()` and `setSelectedShapes()` replace the current selection entirely. Use `deselect()` to remove specific shapes while keeping others selected. Selection changes are recorded in history without clearing the redo stack.
|
||||
|
||||
### Select all
|
||||
|
||||
[Editor#selectAll](?) selects all unlocked shapes, scoped by the current selection:
|
||||
|
||||
```typescript
|
||||
editor.selectAll()
|
||||
```
|
||||
|
||||
- If nothing is selected, it selects all shapes on the current page
|
||||
- If the selected shapes share a common parent (like shapes inside a group), it selects all shapes within that parent
|
||||
- If the selected shapes have different parents, it does nothing
|
||||
|
||||
### Adjacent selection
|
||||
|
||||
Use [Editor#selectAdjacentShape](?) to select the next or previous shape in reading order, or to move by cardinal direction:
|
||||
|
||||
```typescript
|
||||
editor.selectAdjacentShape('next')
|
||||
editor.selectAdjacentShape('prev')
|
||||
editor.selectAdjacentShape('left')
|
||||
editor.selectAdjacentShape('right')
|
||||
editor.selectAdjacentShape('up')
|
||||
editor.selectAdjacentShape('down')
|
||||
```
|
||||
|
||||
Cardinal directions score candidate shapes by distance and by how far they sit off the axis of travel, then pick the lowest score. If the selection is inside a group or frame, only siblings in that container are considered. Shapes whose util returns `false` from [ShapeUtil#canTabTo](?) are skipped. In the default UI, Tab and Shift+Tab move to the next and previous shape, and Cmd/Ctrl+Arrow moves by direction.
|
||||
|
||||
### Hierarchical selection
|
||||
|
||||
Use [Editor#selectParentShape](?) and [Editor#selectFirstChildShape](?) to move up and down the shape hierarchy:
|
||||
|
||||
```typescript
|
||||
// Select the parent of the currently selected shape
|
||||
editor.selectParentShape()
|
||||
|
||||
// Select the first child of the currently selected shape
|
||||
editor.selectFirstChildShape()
|
||||
```
|
||||
|
||||
`selectParentShape()` only acts when exactly one shape is selected. `selectFirstChildShape()` picks the first child (in reading order) of the first selected shape. Both zoom to the new selection if it's offscreen. In the default UI these are bound to Cmd/Ctrl+Shift+Up and Cmd/Ctrl+Shift+Down.
|
||||
|
||||
## Single shape helpers
|
||||
|
||||
When you need to work with exactly one selected shape, use [Editor#getOnlySelectedShapeId](?) and [Editor#getOnlySelectedShape](?):
|
||||
|
||||
```typescript
|
||||
// Get the ID if exactly one shape is selected, null otherwise
|
||||
const id = editor.getOnlySelectedShapeId()
|
||||
|
||||
// Get the shape if exactly one shape is selected, null otherwise
|
||||
const shape = editor.getOnlySelectedShape()
|
||||
```
|
||||
|
||||
Both methods return `null` if zero shapes or multiple shapes are selected.
|
||||
|
||||
### Selected shape at point
|
||||
|
||||
To find which selected shape is at a specific point (useful for hit testing during interactions), use [Editor#getSelectedShapeAtPoint](?):
|
||||
|
||||
```typescript
|
||||
const shape = editor.getSelectedShapeAtPoint({ x: 100, y: 200 })
|
||||
```
|
||||
|
||||
This returns the top-most selected shape at the given point, ignoring groups. It returns `undefined` if no selected shape is at that point.
|
||||
|
||||
## Selection bounds
|
||||
|
||||
The editor computes bounds for the current selection in two ways: axis-aligned and rotated.
|
||||
|
||||
### Axis-aligned bounds
|
||||
|
||||
[Editor#getSelectionPageBounds](?) returns the axis-aligned bounding box that contains all selected shapes:
|
||||
|
||||
```typescript
|
||||
const bounds = editor.getSelectionPageBounds()
|
||||
if (bounds) {
|
||||
console.log(bounds.x, bounds.y, bounds.width, bounds.height)
|
||||
}
|
||||
```
|
||||
|
||||
If the selection includes rotated shapes, these bounds represent the smallest axis-aligned box that contains the rotated shapes. The method returns `null` if nothing is selected.
|
||||
|
||||
### Rotated bounds
|
||||
|
||||
[Editor#getSelectionRotatedPageBounds](?) returns bounds that respect the shared rotation of the selection:
|
||||
|
||||
```typescript
|
||||
const rotatedBounds = editor.getSelectionRotatedPageBounds()
|
||||
```
|
||||
|
||||
The selection box UI uses this for display. If all selected shapes share the same rotation, the bounds rotate with them. If shapes have different rotations, this falls back to axis-aligned bounds. It returns `undefined` if nothing is selected.
|
||||
|
||||
You can access the shared rotation angle via [Editor#getSelectionRotation](?), which returns `0` if shapes have different rotations.
|
||||
|
||||
### Screen space bounds
|
||||
|
||||
Both bound types have screen-space equivalents, [Editor#getSelectionScreenBounds](?) and [Editor#getSelectionRotatedScreenBounds](?), that account for the camera's zoom and pan:
|
||||
|
||||
```typescript
|
||||
const screenBounds = editor.getSelectionScreenBounds()
|
||||
const rotatedScreenBounds = editor.getSelectionRotatedScreenBounds()
|
||||
```
|
||||
|
||||
## Selection rules
|
||||
|
||||
The editor automatically enforces selection consistency through store side effects.
|
||||
|
||||
### Ancestor-descendant filtering
|
||||
|
||||
When the selection changes, the editor filters out any shape whose ancestor is also selected:
|
||||
|
||||
```typescript
|
||||
// If you try to select a shape and its parent, only the parent remains selected
|
||||
editor.select(groupId, childOfGroupId)
|
||||
// Result: only groupId is selected
|
||||
```
|
||||
|
||||
This prevents ambiguous situations where both a container and its contents are selected. The filtering happens in the `instance_page_state` after-change side effect.
|
||||
|
||||
### Focused group management
|
||||
|
||||
When you select shapes that are children of a group, the editor automatically updates the focused group. The focused group is the group that defines the current editing scope: while a group is focused, clicks select shapes inside it rather than the group itself. See [Groups](/sdk-features/groups) for details.
|
||||
|
||||
```typescript
|
||||
// Selecting shapes inside a group focuses that group
|
||||
editor.select(shapeInsideGroup)
|
||||
// The group becomes the focused group
|
||||
```
|
||||
|
||||
If all selected shapes share a common group ancestor, that group becomes focused. If you select shapes without a common group ancestor, the editor clears the focused group. Clearing the selection leaves the focused group in place.
|
||||
|
||||
## Locked shapes
|
||||
|
||||
The editor excludes locked shapes from bulk selection operations:
|
||||
|
||||
```typescript
|
||||
// selectAll only selects unlocked shapes
|
||||
editor.selectAll()
|
||||
|
||||
// Operations like delete and duplicate also respect locks
|
||||
editor.deleteShapes(shapeIds) // Only deletes unlocked shapes
|
||||
```
|
||||
|
||||
Locks don't restrict individual shape selection through `select()`. You can still select locked shapes explicitly when needed. By default, users can't select locked shapes by clicking or brushing; the `selectLockedShapes` option in [TldrawOptions](?) allows that while keeping the shapes protected from edits. See [Locked shapes](/sdk-features/locked-shapes).
|
||||
|
||||
## Ancestor checking
|
||||
|
||||
To determine if a shape's ancestor is selected, use [Editor#isAncestorSelected](?):
|
||||
|
||||
```typescript
|
||||
const hasSelectedAncestor = editor.isAncestorSelected(shape)
|
||||
```
|
||||
|
||||
This walks up the shape's parent chain and returns `true` if any ancestor is in the current selection.
|
||||
|
||||
## Related examples
|
||||
|
||||
- [Selection UI](/examples/ui/selection-ui) - Add custom UI elements that appear around the current selection using selection bounds.
|
||||
- [Prevent multi-shape selection](/examples/events/prevent-multi-shape-selection) - Use side effects to restrict selection to a single shape at a time.
|
||||
- [Lasso select tool](/examples/editor-api/lasso-select-tool) - Create a custom freehand selection tool.
|
||||
Reference in New Issue
Block a user