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

This commit is contained in:
upstream-mirror
2026-09-23 16:12:52 +05:00
commit a774ac673a
4541 changed files with 770499 additions and 0 deletions

View 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.