apps/docs/content/sdk-features/draw-shape.mdx
The draw shape captures freehand strokes and straight line segments. It supports pressure-sensitive input, automatic shape closing, angle snapping, and hybrid freehand/straight-line drawing modes. The draw tool detects pen and stylus input and produces variable-width strokes that respond to pressure.
The draw tool supports two segment types that you can switch between while drawing:
| Mode | Trigger | Behavior |
|---|---|---|
| Freehand | Default | Captures natural hand motion with optional pressure sensitivity |
| Straight | Hold Shift while drawing | Creates straight line segments that snap to 15° angle increments |
You can mix both modes in a single stroke. Start drawing freehand, then hold Shift to switch to straight lines. Release Shift to return to freehand. Each mode change creates a new segment in the shape.
Freehand mode captures your natural hand motion. The tool records points as you drag and interpolates them into smooth curves. When you draw with a pen or stylus, the tool captures pressure data and produces variable-width strokes.
The stroke appearance depends on the dash style:
Hold Shift while drawing to create straight line segments. The line snaps to 15° angle increments relative to the previous point, making it easy to draw horizontal, vertical, and diagonal lines.
Release Shift to continue with freehand drawing from the current endpoint. The transition creates a smooth connection between the straight segment and the freehand stroke.
If you've already drawn a stroke and want to continue from it, hold Shift and click to connect. The draw tool creates a straight line segment from the previous stroke's endpoint to your click position. Continue holding Shift and drag to extend with more straight segments, or release Shift to switch to freehand.
This connect-the-dots behavior only activates when you Shift+click after completing a previous stroke with the same draw tool session. It won't connect across different shapes or after switching tools.
The draw tool distinguishes between mouse/touch input and pen/stylus input. When it detects a pen or stylus, it enables pressure-sensitive rendering:
| Input type | Pressure behavior |
|---|---|
| Mouse/touch | Simulates pressure based on velocity—faster strokes appear thinner |
| Pen/stylus | Uses actual pressure data for variable stroke width |
The tool detects stylus input through the pressure value in pointer events. Values between 0 and 0.5 (exclusive) or between 0.5 and 1 (exclusive) indicate stylus input, as mice report exactly 0.5.
The shape stores pen detection in the isPen property. This affects how the stroke renders—pen strokes use a different stroke profile optimized for real pressure data.
Draw shapes can automatically close when you bring the endpoint near the starting point. This creates filled shapes when combined with a fill style other than "none".
The shape closes when:
When a shape closes, the shape sets isClosed to true and fills according to its fill style. Highlight shapes don't support closing.
When drawing straight lines with Shift held, you can snap to previous segments in the current stroke. This helps create precise geometric constructions:
Visual snap indicators appear when snapping is active.
Straight line segments snap to 15° increments (24 divisions of a full circle). This makes it easy to draw:
Hold Ctrl while in straight line mode to disable angle snapping temporarily.
When dynamic resize mode is enabled in user preferences, new draw shapes scale inversely with zoom level. Drawing while zoomed out creates shapes that appear the same size on screen as they would at 100% zoom. The shape's scale property stores this adjustment.
Access dynamic resize mode through Editor#user:
// Check current mode
const isDynamic = editor.user.getIsDynamicResizeMode()
// Enable dynamic resize mode
editor.user.updateUserPreferences({ isDynamicResizeMode: true })
Draw shapes store their path data in an efficient delta-encoded base64 format. The first point uses full Float32 precision (12 bytes), with subsequent points stored as Float16 deltas (6 bytes each).
| Property | Type | Description |
|---|---|---|
color | TLDefaultColorStyle | Stroke color |
fill | TLDefaultFillStyle | Fill style (applies when isClosed is true) |
dash | TLDefaultDashStyle | Stroke pattern: draw, solid, dashed, dotted |
size | TLDefaultSizeStyle | Stroke width preset: s, m, l, xl |
segments | TLDrawShapeSegment[] | Array of segments with type and base64-encoded path |
isComplete | boolean | Whether the user has finished drawing this stroke |
isClosed | boolean | Whether the path forms a closed shape |
isPen | boolean | Whether drawn with a stylus (enables pressure-based width) |
scale | number | Scale factor applied to the shape |
scaleX | number | Horizontal scale factor for lazy resize |
scaleY | number | Vertical scale factor for lazy resize |
Each segment has a type of 'free' or 'straight' and a path containing the encoded point data with x, y, and z (pressure) values.
Configure the draw shape utility to adjust behavior:
| Option | Type | Default | Description |
|---|---|---|---|
maxPointsPerShape | number | 600 | Maximum points before automatically starting a new shape |
import { DrawShapeUtil } from 'tldraw'
const ConfiguredDrawUtil = DrawShapeUtil.configure({
maxPointsPerShape: 1000,
})
When a stroke exceeds the maximum point count, the draw tool completes the current shape and creates a new one at the current position. This prevents performance issues with very long strokes.
To create a draw shape through the editor API, you need to encode the point data:
import { b64Vecs, createShapeId } from '@tldraw/editor'
// Define your points with x, y, and z (pressure)
const points = [
{ x: 0, y: 0, z: 0.5 },
{ x: 50, y: 30, z: 0.5 },
{ x: 100, y: 10, z: 0.5 },
]
editor.createShape({
id: createShapeId(),
type: 'draw',
x: 100,
y: 100,
props: {
color: 'black',
fill: 'none',
dash: 'draw',
size: 'm',
segments: [
{
type: 'free',
path: b64Vecs.encodePoints(points),
},
],
isComplete: true,
isClosed: false,
isPen: false,
scale: 1,
scaleX: 1,
scaleY: 1,
},
})
The b64Vecs.encodePoints function converts an array of point objects to the delta-encoded base64 format. Use b64Vecs.decodePoints to read points back from a segment's path.
The draw shape uses a freehand stroke algorithm to render organic-looking lines. The algorithm applies:
When dash is set to 'draw', the shape renders using the full freehand algorithm. Other dash styles use simpler uniform-width strokes with the appropriate dash pattern.
At low zoom levels, the shape automatically switches to solid rendering for performance. This happens when the zoom level is below 50% and also below a threshold based on stroke width (zoomLevel < 1.5 / strokeWidth).
The shape's geometry depends on its content:
The geometry uses the processed stroke points (after applying streamline and smoothing), not the raw input points.