Custom shape
You can create custom shapes in tldraw by creating a shape util and passing it to the Tldraw component. In this example, we'll create a custom shape that is a simple rectangle with some text inside of it.
import {
Geometry2d,
HTMLContainer,
RecordProps,
Rectangle2d,
ShapeUtil,
T,
TLBaseShape,
TLResizeInfo,
Tldraw,
resizeBox,
} from 'tldraw'
import 'tldraw/tldraw.css'
// There's a guide at the bottom of this file!
// [1]
type ICustomShape = TLBaseShape<
'my-custom-shape',
{
w: number
h: number
text: string
}
>
// [2]
export class MyShapeUtil extends ShapeUtil<ICustomShape> {
// [a]
static override type = 'my-custom-shape' as const
static override props: RecordProps<ICustomShape> = {
w: T.number,
h: T.number,
text: T.string,
}
// [b]
getDefaultProps(): ICustomShape['props'] {
return {
w: 200,
h: 200,
text: "I'm a shape!",
}
}
// [c]
override canEdit() {
return false
}
override canResize() {
return true
}
override isAspectRatioLocked() {
return false
}
// [d]
getGeometry(shape: ICustomShape): Geometry2d {
return new Rectangle2d({
width: shape.props.w,
height: shape.props.h,
isFilled: true,
})
}
// [e]
override onResize(shape: any, info: TLResizeInfo<any>) {
return resizeBox(shape, info)
}
// [f]
component(shape: ICustomShape) {
return <HTMLContainer style={{ backgroundColor: '#efefef' }}>{shape.props.text}</HTMLContainer>
}
// [g]
indicator(shape: ICustomShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
}
// [3]
const customShape = [MyShapeUtil]
export default function CustomShapeExample() {
return (
<div className="tldraw__editor">
<Tldraw
shapeUtils={customShape}
onMount={(editor) => {
editor.createShape({ type: 'my-custom-shape', x: 100, y: 100 })
}}
/>
</div>
)
}
/*
Introduction:
You can create custom shapes in tldraw by creating a shape util and passing it to the Tldraw component.
In this example, we'll create a custom shape that is a simple rectangle with some text inside of it.
[1]
Define the shape type. This is a type that extend the `TLBaseShape` generic and defines the shape's
props. We need to pass in a unique string literal for the shape's type and an object that defines the
shape's props.
[2]
This is our shape util. In tldraw shape utils are classes that define how a shape behaves and renders.
We can extend the ShapeUtil class and provide the shape type as a generic. If we extended the
BaseBoxShapeUtil class instead, we wouldn't have define methods such as `getGeometry` and `onResize`.
[a]
This is where we define out shape's props and type for the editor. It's important to use the same
string for the type as we did in [1]. We need to define the shape's props using tldraw's validator
library. The validator will help make sure the store always has shape data we can trust.
[b]
This is a method that returns the default props for our shape.
[c]
Some handy methods for controlling different shape behaviour. You don't have to define these, and
they're only shown here so you know they exist. Check out the editable shape example to learn more
about creating an editable shape.
[d]
The getGeometry method is what the editor uses for hit-testing, binding etc. We're using the
Rectangle2d class from tldraw's geometry library to create a rectangle shape. If we extended the
BaseBoxShapeUtil class, we wouldn't have to define this method.
[e]
We're using the resizeBox utility method to handle resizing our shape. If we extended the
BaseBoxShapeUtil class, we wouldn't have to define this method.
[f]
The component method defines how our shape renders. We're returning an HTMLContainer here, which
is a handy component that tldraw exports. It's essentially a div with some special css. There's a
lot of flexibility here, and you can use any React hooks you want and return any valid JSX.
[g]
The indicator is the blue outline around a selected shape. We're just returning a rectangle with the
same width and height as the shape here. You can return any valid JSX here.
[3]
This is where we render the Tldraw component with our custom shape. We're passing in our custom shape
util as an array to the shapeUtils prop. We're also using the onMount callback to create a shape on
the canvas. If you want to learn how to add a tool for your shape, check out the custom config example.
If you want to learn how to programmatically control the canvas, check out the Editor API examples.
*/
Prev
Sticker (bindings)Next
Custom tool