Back to Tldraw

Indicators

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

5.4.05.3 KB
Original Source

Indicators are the colored outlines that appear around shapes when they're selected, hovered, or being interacted with. Every shape defines its own indicator through the ShapeUtil#getIndicatorPath method.

How indicators work

When you hover over or select a shape, tldraw draws an outline that matches the shape's geometry. The indicator is separate from the shape itself: the ShapeIndicatorOverlayUtil strokes it on a canvas overlay above the shapes, in the theme's selection color.

Indicators appear in three contexts:

ContextWhen it appearsStroke weight
SelectedShape is in the current selection1.5px
HoveredPointer is over an unselected shape while the select tool is idle (fine pointers only, not touch)1.5px
HintingShape is a drop target during drag operations2.5px

For collaborative editing, CollaboratorShapeIndicatorOverlayUtil also shows which shapes other users have selected, in each collaborator's cursor color. Locked shapes never show indicators.

Defining an indicator

Every ShapeUtil must implement the getIndicatorPath method. This method returns a Path2D, a richer TLIndicatorPath object for indicators that need clipping or additional stroked paths, or undefined to draw no indicator:

tsx
import { ShapeUtil, TLShape, Rectangle2d, T, RecordProps } from 'tldraw'

declare module 'tldraw' {
	export interface TLGlobalShapePropsMap {
		myshape: { w: number; h: number }
	}
}

type MyShape = TLShape<'myshape'>

class MyShapeUtil extends ShapeUtil<MyShape> {
	static override type = 'myshape' as const
	static override props: RecordProps<MyShape> = {
		w: T.number,
		h: T.number,
	}

	getDefaultProps() {
		return { w: 100, h: 100 }
	}

	getGeometry(shape: MyShape) {
		return new Rectangle2d({
			width: shape.props.w,
			height: shape.props.h,
			isFilled: true,
		})
	}

	component(shape: MyShape) {
		return <div style={{ width: shape.props.w, height: shape.props.h }} />
	}

	getIndicatorPath(shape: MyShape) {
		const path = new Path2D()
		path.rect(0, 0, shape.props.w, shape.props.h)
		return path
	}
}

The getIndicatorPath method receives the shape and returns paths in the shape's local coordinate space. You don't need to set stroke color or width—tldraw applies those automatically based on context.

Common indicator patterns

For circular shapes, use an ellipse path:

tsx
getIndicatorPath(shape: MyShape) {
	const { w, h } = shape.props
	const path = new Path2D()
	path.ellipse(w / 2, h / 2, w / 2, h / 2, 0, 0, Math.PI * 2)
	return path
}

For complex paths, use the shape's geometry:

tsx
getIndicatorPath(shape: MyShape) {
	const geometry = this.editor.getShapeGeometry(shape)
	return new Path2D(geometry.toSimpleSvgPath())
}

Indicators with labels

Shapes with labels may need to clip the indicator where the label appears. Arrow shapes do this to prevent the indicator from overlapping label text. Return an object with path, an optional clipPath, and optional additionalPaths. The clip is applied even-odd before stroking path, so an outer rectangle plus the label rectangle punches a hole for the label. additionalPaths are stroked afterwards without the clip:

tsx
override getIndicatorPath(shape: MyShape) {
	const path = new Path2D()
	path.moveTo(0, 0)
	path.lineTo(shape.props.w, shape.props.h)

	// Even-odd: the outer rect keeps everything, the inner rect punches a hole
	const clipPath = new Path2D()
	clipPath.rect(-100, -100, shape.props.w + 200, shape.props.h + 200)
	clipPath.rect(40, 40, 20, 20)

	return { path, clipPath }
}

Hinting shapes

Hinting shapes are shapes that receive a highlighted indicator during drag operations. Use Editor#setHintingShapes to mark shapes as drop targets:

tsx
// Highlight a shape as a potential drop target
editor.setHintingShapes([targetShapeId])

// Clear hinting
editor.setHintingShapes([])

Customizing indicators

The stroke widths and paint order live on ShapeIndicatorOverlayUtil.options (lineWidth, hintedLineWidth, zIndex). Adjust them with configure, or subclass the util and override getOverlays() to change which shapes get indicators, then pass it through the overlayUtils prop. See Overlay utils for how overlay utils are registered and replaced.

tsx
import { ShapeIndicatorOverlayUtil, Tldraw } from 'tldraw'

const ThickIndicators = ShapeIndicatorOverlayUtil.configure({ lineWidth: 3 })

function App() {
	return <Tldraw overlayUtils={[ThickIndicators]} />
}
  • Shapes - Learn how to create custom shapes with their own indicators
  • Selection - Understand how selection state controls indicator visibility
  • Overlay utils - The overlay system that draws indicators