Documentation Index Fetch the complete documentation index at: https://mintlify.com/tldraw/tldraw/llms.txt
Use this file to discover all available pages before exploring further.
The geometry system provides mathematical representations of shapes for hit testing, bounds calculation, snapping, and collision detection. Every shape returns a Geometry2d primitive from its getGeometry() method.
Geometry2d primitives
All geometry classes extend the abstract Geometry2d base class:
abstract class Geometry2d {
isFilled : boolean // Whether the interior counts for hit tests
isClosed : boolean // Whether the shape forms a closed loop
isLabel : boolean // Whether this is a label geometry
isInternal : boolean // Whether to exclude from certain operations
abstract getVertices () : Vec []
abstract nearestPoint ( point : VecLike ) : Vec
abstract getSvgPathData () : string
}
Available primitives
Rectangle2d Axis-aligned rectangles with optimized bounds calculation
Polygon2d Closed polygons with arbitrary vertices
Polyline2d Open polylines (non-closed paths)
Circle2d Perfect circles defined by center and radius
Ellipse2d Ellipses with independent width and height
Arc2d Circular arcs with start/end angles
Edge2d Single line segments
Point2d Single points in space
Group2d Composite geometry containing multiple children
CubicBezier2d Cubic Bézier curves with control points
CubicSpline2d Smooth splines through multiple points
Basic primitives
Rectangle2d
Optimized for rectangular shapes:
import { Rectangle2d } from 'tldraw'
getGeometry ( shape : MyShape ) {
return new Rectangle2d ({
x: 0 ,
y: 0 ,
width: shape . props . w ,
height: shape . props . h ,
isFilled: true ,
isClosed: true ,
})
}
X coordinate of top-left corner
Y coordinate of top-left corner
Whether interior counts for hit testing
Polygon2d
For closed shapes with custom vertices:
import { Polygon2d , Vec } from 'tldraw'
getGeometry ( shape : MyTriangleShape ) {
const w = shape . props . w
const h = shape . props . h
return new Polygon2d ({
points: [
new Vec ( w / 2 , 0 ), // Top
new Vec ( w , h ), // Bottom right
new Vec ( 0 , h ), // Bottom left
],
isFilled: shape . props . fill !== 'none' ,
isClosed: true ,
})
}
Polygon2d requires at least 3 points. Use Edge2d for line segments.
Polyline2d
For open paths (non-closed):
import { Polyline2d } from 'tldraw'
getGeometry ( shape : MyDrawShape ) {
return new Polyline2d ({
points: shape . props . segments . map ( seg => new Vec ( seg . x , seg . y )),
isFilled: false ,
isClosed: false , // Not a closed loop
})
}
Circle2d and Ellipse2d
import { Circle2d , Ellipse2d } from 'tldraw'
// Perfect circle
const circle = new Circle2d ({
x: 50 ,
y: 50 ,
radius: 50 ,
isFilled: true ,
})
// Ellipse
const ellipse = new Ellipse2d ({
x: 0 ,
y: 0 ,
width: 100 ,
height: 50 ,
isFilled: true ,
})
Edge2d
For single line segments:
import { Edge2d , Vec } from 'tldraw'
const edge = new Edge2d ({
start: new Vec ( 0 , 0 ),
end: new Vec ( 100 , 100 ),
})
Composite geometry
Group2d
Combine multiple geometries:
import { Group2d , Rectangle2d , Polygon2d } from 'tldraw'
getGeometry ( shape : TLGeoShape ) {
const bodyGeometry = getShapeBodyGeometry ( shape )
const labelGeometry = new Rectangle2d ({
x: labelBounds . x ,
y: labelBounds . y ,
width: labelBounds . w ,
height: labelBounds . h ,
isFilled: true ,
isLabel: true , // Mark as label geometry
})
return new Group2d ({
children: [ bodyGeometry , labelGeometry ],
})
}
Group2d automatically calculates combined bounds and handles hit testing across all children.
Filtering child geometries
Use filters to control which geometries are included in operations:
import { Geometry2dFilters } from 'tldraw'
// Exclude labels from calculations
const vertices = geometry . getVertices ( Geometry2dFilters . EXCLUDE_LABELS )
// Include everything
const allVertices = geometry . getVertices ( Geometry2dFilters . INCLUDE_ALL )
// Custom filter
const customVertices = geometry . getVertices ({
includeLabels: false ,
includeInternal: true ,
})
Curved geometries
Arc2d
For circular arcs:
import { Arc2d , Vec } from 'tldraw'
const arc = new Arc2d ({
center: new Vec ( 50 , 50 ),
start: new Vec ( 100 , 50 ),
end: new Vec ( 50 , 100 ),
sweepFlag: 1 , // Clockwise
largeArcFlag: 0 , // Small arc
})
CubicBezier2d
For Bézier curves:
import { CubicBezier2d , Vec } from 'tldraw'
const bezier = new CubicBezier2d ({
start: new Vec ( 0 , 0 ),
cp1: new Vec ( 25 , 50 ), // First control point
cp2: new Vec ( 75 , 50 ), // Second control point
end: new Vec ( 100 , 0 ),
})
CubicSpline2d
For smooth curves through points:
import { CubicSpline2d , Vec } from 'tldraw'
const spline = new CubicSpline2d ({
points: [
new Vec ( 0 , 0 ),
new Vec ( 50 , 100 ),
new Vec ( 100 , 50 ),
new Vec ( 150 , 0 ),
],
})
Geometry operations
Hit testing
// Point hit test
const isHit = geometry . hitTestPoint (
{ x: 50 , y: 50 },
5 , // margin in pixels
true // hitInside - whether interior counts
)
// Line segment hit test
const hitsLine = geometry . hitTestLineSegment (
{ x: 0 , y: 0 },
{ x: 100 , y: 100 },
5 // distance threshold
)
Distance calculations
// Distance to a point (negative if inside)
const distance = geometry . distanceToPoint (
{ x: 50 , y: 50 },
true // hitInside
)
// Distance to a line segment
const distToLine = geometry . distanceToLineSegment (
{ x: 0 , y: 0 },
{ x: 100 , y: 100 }
)
// Nearest point on geometry
const nearest = geometry . nearestPoint ({ x: 50 , y: 50 })
Bounds and area
// Bounding box
const bounds = geometry . bounds // Box { minX, minY, maxX, maxY, width, height }
const center = geometry . center // Vec { x, y }
// Area (for closed, filled shapes)
const area = geometry . area
// Perimeter length
const length = geometry . length
Vertices and paths
// Get vertices
const vertices = geometry . vertices // Vec[]
// Get SVG path data
const pathData = geometry . getSvgPathData ()
// Example: "M0,0 L100,0 L100,100 L0,100 Z"
// Get simple SVG path
const simplePath = geometry . toSimpleSvgPath ()
Intersections
// Intersect with line segment
const lineIntersections = geometry . intersectLineSegment (
{ x: 0 , y: 50 },
{ x: 100 , y: 50 }
) // VecLike[]
// Intersect with circle
const circleIntersections = geometry . intersectCircle (
{ x: 50 , y: 50 }, // center
25 // radius
)
// Intersect with polygon
const polyIntersections = geometry . intersectPolygon ([
{ x: 0 , y: 0 },
{ x: 100 , y: 0 },
{ x: 100 , y: 100 },
{ x: 0 , y: 100 },
])
// Intersect with polyline (open path)
const polylineIntersections = geometry . intersectPolyline ([
{ x: 0 , y: 0 },
{ x: 50 , y: 100 },
{ x: 100 , y: 0 },
])
Overlap testing
// Test if geometry overlaps a polygon
const overlaps = geometry . overlapsPolygon ([
{ x: 0 , y: 0 },
{ x: 100 , y: 0 },
{ x: 100 , y: 100 },
{ x: 0 , y: 100 },
])
Edge interpolation
Find points along the geometry’s edge:
// Get point at 50% along the edge
const midpoint = geometry . interpolateAlongEdge ( 0.5 )
// Get point at 25% along the edge
const quarterPoint = geometry . interpolateAlongEdge ( 0.25 )
// Reverse: find position of a point along the edge
const t = geometry . uninterpolateAlongEdge ({ x: 50 , y: 25 })
// Returns 0-1 representing position along edge
Use interpolateAlongEdge() to position elements along a shape’s perimeter, like arrow labels.
Apply matrix transformations to geometry:
import { Mat } from 'tldraw'
// Create a rotation matrix
const matrix = Mat . Compose (
Mat . Translate ( 50 , 50 ),
Mat . Rotate ( Math . PI / 4 ),
Mat . Translate ( - 50 , - 50 )
)
// Transform the geometry
const transformed = geometry . transform ( matrix , {
isLabel: false ,
debugColor: '#ff0000' ,
})
Non-uniform scaling is not yet supported in transformed geometries.
Custom geometry example
Create a star shape:
import { Polygon2d , Vec } from 'tldraw'
function createStarGeometry ( cx : number , cy : number , points : number , outerRadius : number , innerRadius : number ) {
const vertices : Vec [] = []
const angleStep = ( Math . PI * 2 ) / ( points * 2 )
for ( let i = 0 ; i < points * 2 ; i ++ ) {
const angle = i * angleStep - Math . PI / 2
const radius = i % 2 === 0 ? outerRadius : innerRadius
vertices . push (
new Vec (
cx + Math . cos ( angle ) * radius ,
cy + Math . sin ( angle ) * radius
)
)
}
return new Polygon2d ({
points: vertices ,
isFilled: true ,
isClosed: true ,
})
}
class StarShapeUtil extends ShapeUtil < StarShape > {
getGeometry ( shape : StarShape ) {
return createStarGeometry (
shape . props . w / 2 ,
shape . props . h / 2 ,
5 , // 5 points
shape . props . w / 2 ,
shape . props . w / 4
)
}
}
Caching
Complexity
Group optimization
Geometry is automatically cached by the editor: // This is cached - only recalculated when shape changes
const geometry = editor . getShapeGeometry ( shape )
Don’t create expensive geometries in component() or indicator() - use getGeometry() instead. Keep vertex counts reasonable:
Simple shapes : < 100 vertices
Complex shapes : < 1000 vertices
Very complex : Use simplified geometry for hit testing
getGeometry ( shape : ComplexShape , opts ?: TLGeometryOpts ) {
if ( opts ?. context === 'hit-test' ) {
// Return simplified geometry for hit testing
return new Rectangle2d ({ /* bounds */ })
}
// Return detailed geometry for rendering
return detailedGeometry
}
Use filters to exclude unnecessary geometries: const bodyGeometry = new Rectangle2d ({
width: 100 ,
height: 100 ,
isFilled: true ,
})
const debugGeometry = new Rectangle2d ({
width: 100 ,
height: 100 ,
isInternal: true , // Excluded by default
debugColor: '#ff0000' ,
})
return new Group2d ({
children: [ bodyGeometry , debugGeometry ],
})
Next steps
Shape rendering Learn how to render shapes on the canvas
Shape interactions Handle user interactions with shapes