--- title: Indicators created_at: 01/31/2026 updated_at: 01/31/2026 keywords: - indicator - selection - hover - outline - ShapeUtil - getIndicatorPath - hinting status: published date: 01/31/2026 --- 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: | Context | When it appears | Stroke weight | | -------- | ------------------------------------------------------------------------------------------------- | ------------- | | Selected | Shape is in the current selection | 1.5px | | Hovered | Pointer is over an unselected shape while the select tool is idle (fine pointers only, not touch) | 1.5px | | Hinting | Shape is a drop target during drag operations | 2.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 { static override type = 'myshape' as const static override props: RecordProps = { 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
} 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](/sdk-features/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 } ``` ## Related articles - [Shapes](/sdk-features/shapes) - Learn how to create custom shapes with their own indicators - [Selection](/sdk-features/selection) - Understand how selection state controls indicator visibility - [Overlay utils](/sdk-features/overlay-utils) - The overlay system that draws indicators ## Related examples - [Custom shape](/examples/shapes/tools/custom-shape) - Create a custom shape with an indicator - [Custom indicators](/examples/ui/indicators-logic) - Control which shapes show indicators