Back to Tldraw

Selection

apps/docs/content/sdk-features/selection.mdx

5.4.08.3 KB
Original Source

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

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.