Indicators

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

How indicators work

When you hover over or select a shape, tldraw renders an SVG outline that matches the shape's geometry. The indicator is separate from the shape itself—it renders in an overlay layer above the canvas.

Indicators appear in three contexts:

ContextWhen it appearsStroke weight
SelectedShape is in the current selection1.5px
HoveredPointer is over an unselected shape1.5px
HintingShape is a drop target during drag operations2.5px

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

Defining an indicator

Every ShapeUtil must implement the indicator method. This method returns SVG elements that outline the shape:

import { ShapeUtil, TLBaseShape, Rectangle2d, T, RecordProps } from 'tldraw'

type MyShape = TLBaseShape<'myshape', { w: number; h: number }>

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 }} />
	}

	indicator(shape: MyShape) {
		return <rect width={shape.props.w} height={shape.props.h} />
	}
}

The indicator method receives the shape and returns SVG elements. You don't need to set stroke color or width—tldraw applies those automatically based on context.

Common indicator patterns

For rectangular shapes, return a <rect>:

indicator(shape: MyShape) {
	return <rect width={shape.props.w} height={shape.props.h} />
}

For circular shapes, use an ellipse or circle:

indicator(shape: MyShape) {
	const { w, h } = shape.props
	return <ellipse cx={w / 2} cy={h / 2} rx={w / 2} ry={h / 2} />
}

For complex paths, use the shape's geometry:

indicator(shape: MyShape) {
	const path = this.editor.getShapeGeometry(shape).toSvgPathString()
	return <path d={path} />
}

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 a more complex SVG structure to handle clipping.

Canvas-based indicators

For performance-critical shapes, you can implement getIndicatorPath() to render indicators directly to a canvas instead of using SVG. Canvas rendering performs better with large selections.

class MyShapeUtil extends ShapeUtil<MyShape> {
	// Return false to use canvas rendering
	override useLegacyIndicator() {
		return false
	}

	// Return a Path2D for canvas rendering
	override getIndicatorPath(shape: MyShape): Path2D {
		const path = new Path2D()
		path.rect(0, 0, shape.props.w, shape.props.h)
		return path
	}

	// Still required as fallback
	indicator(shape: MyShape) {
		return <rect width={shape.props.w} height={shape.props.h} />
	}
}

When useLegacyIndicator() returns false, tldraw calls getIndicatorPath() instead of indicator(). Tldraw strokes the Path2D directly on an overlay canvas.

For shapes that need clipping (like arrows with labels), return an object with additional path information:

override getIndicatorPath(shape: MyShape) {
	return {
		path: mainPath,
		clipPath: labelClipPath,
		additionalPaths: [extraPath],
	}
}

Controlling indicator visibility

The DefaultShapeIndicators component manages which indicators appear. By default, indicators show for:

  • All shapes in the current selection
  • The shape under the pointer (when hovering)
  • Shapes marked as "hinting" (drop targets during drag operations)

Show or hide all indicators

Override the ShapeIndicators component to control visibility globally:

import { Tldraw, DefaultShapeIndicators, TLComponents } from 'tldraw'
import 'tldraw/tldraw.css'

const components: TLComponents = {
	ShapeIndicators: () => {
		// Hide all indicators
		return <DefaultShapeIndicators hideAll />
	},
}

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw components={components} />
		</div>
	)
}

Pass showAll to always show indicators for every visible shape. Pass hideAll to suppress them entirely.

Custom indicator logic

For custom visibility logic, override ShapeIndicators completely:

import { Tldraw, TLComponents, useEditor, useEditorComponents, useValue } from 'tldraw'
import 'tldraw/tldraw.css'

const components: TLComponents = {
	ShapeIndicators: () => {
		const editor = useEditor()
		const { ShapeIndicator } = useEditorComponents()

		const shapesToShow = useValue(
			'shapes with indicators',
			() => {
				// Show indicators only for geo shapes
				return editor.getRenderingShapes().filter(({ id }) => {
					const shape = editor.getShape(id)
					return shape?.type === 'geo'
				})
			},
			[editor]
		)

		if (!ShapeIndicator) return null

		return shapesToShow.map(({ id }) => <ShapeIndicator key={id} shapeId={id} />)
	},
}

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw components={components} />
		</div>
	)
}

Hinting shapes

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

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

// Clear hinting
editor.setHintingShapes([])

Hinting indicators render with a thicker stroke (2.5px vs 1.5px) to distinguish them from regular selection.

Customizing the indicator component

Override ShapeIndicator to change how individual indicators render:

import { Tldraw, TLComponents, TLShapeIndicatorProps, DefaultShapeIndicator } from 'tldraw'
import 'tldraw/tldraw.css'

const components: TLComponents = {
	ShapeIndicator: (props: TLShapeIndicatorProps) => {
		// Use a custom color for all indicators
		return <DefaultShapeIndicator {...props} color="red" opacity={0.5} />
	},
}

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw components={components} />
		</div>
	)
}

The TLShapeIndicatorProps interface includes:

PropDescription
shapeIdThe shape's ID
userIdUser ID for collaborator indicators (optional)
colorStroke color (defaults to selection color)
opacityOpacity from 0 to 1
classNameAdditional CSS class
hiddenWhether to hide this indicator
  • Shapes - Learn how to create custom shapes with their own indicators
  • Selection - Understand how selection state controls indicator visibility
  • UI components - Customize canvas components including indicators
Prev
Image export
Next
Input handling