Back to Tldraw

Styles

apps/docs/content/sdk-features/styles.mdx

5.4.09.0 KB
Original Source

The styles system manages visual properties like color, size, font, fill, and dash patterns across shapes. Style properties differ from regular shape properties in two ways: the same value can be set on many shapes at once, and the editor remembers the last-used value and applies it to newly created shapes.

Styles are defined using StyleProp instances that specify valid values and defaults. The editor tracks "shared styles" across the current selection (whether all selected shapes share the same value or have different values) to drive the UI and enable batch updates.

How it works

StyleProp

A StyleProp represents a reusable style property that can be applied across different shape types. Each StyleProp has a unique identifier, a default value, and optional validation.

You define a StyleProp using one of two static methods, StyleProp#define for arbitrary types and StyleProp#defineEnum for a fixed list of values:

typescript
import { StyleProp, T } from 'tldraw'

// Define a numeric style property
const LineWidthStyle = StyleProp.define('myApp:lineWidth', {
	defaultValue: 2,
	type: T.number,
})

// Define an enumerated style property
const CapStyle = StyleProp.defineEnum('myApp:cap', {
	defaultValue: 'round',
	values: ['round', 'square', 'butt'],
})

The unique identifier should be namespaced to avoid conflicts with other style properties. Use your app or library name as a prefix.

Shape integration

To use a style property in your shape, include the StyleProp instance in your shape's props definition. This works the same way for your own styles and for tldraw's defaults. The editor recognizes StyleProp instances and handles them specially: it saves their values, applies them to new shapes, and tracks them across selections.

typescript
import {
	DefaultColorStyle,
	DefaultSizeStyle,
	RecordProps,
	T,
	TLDefaultColorStyle,
	TLDefaultSizeStyle,
	TLShape,
} from 'tldraw'

// Register the shape type and its props
declare module 'tldraw' {
	export interface TLGlobalShapePropsMap {
		'my-shape': {
			w: number
			h: number
			color: TLDefaultColorStyle
			size: TLDefaultSizeStyle
			lineWidth: number
		}
	}
}

type TLMyShape = TLShape<'my-shape'>

// Pass StyleProp instances in the props object for validation
const myShapeProps: RecordProps<TLMyShape> = {
	w: T.number,
	h: T.number,
	color: DefaultColorStyle,
	size: DefaultSizeStyle,
	lineWidth: LineWidthStyle,
}

When you create a shape, provide the actual style values. If you omit a style prop, the editor uses its saved value from previous shapes:

typescript
editor.createShape({
	type: 'my-shape',
	props: {
		w: 100,
		h: 100,
		color: 'red',
		size: 'm',
	},
})

Shared styles

The editor computes shared styles across the current selection. Use Editor#getSharedStyles to get a ReadonlySharedStyleMap from each style property to its SharedStyle status: either "shared" (all shapes have the same value) or "mixed" (shapes have different values).

typescript
const sharedStyles = editor.getSharedStyles()
const colorStyle = sharedStyles.get(DefaultColorStyle)

if (colorStyle && colorStyle.type === 'shared') {
	console.log('All shapes are', colorStyle.value)
} else if (colorStyle && colorStyle.type === 'mixed') {
	console.log('Shapes have different colors')
}

For convenience, use getAsKnownValue when you only care about the shared case:

typescript
const sharedStyles = editor.getSharedStyles()
const color = sharedStyles.getAsKnownValue(DefaultColorStyle)
// Returns the color if all shapes share it, undefined otherwise

The getSharedStyles method examines each selected shape, extracts its style values, and compares them. For groups, it recursively examines the group's children rather than the group itself, since groups don't have visual styles.

When you're not in the select tool with a selection, getSharedStyles returns the styles for the current tool if that tool creates shapes. This lets the UI show and modify the styles that will be applied to the next shape.

Setting styles

Use Editor#setStyleForSelectedShapes to change styles on the current selection:

typescript
// Change color for all selected shapes
editor.setStyleForSelectedShapes(DefaultColorStyle, 'red')

// Change size
editor.setStyleForSelectedShapes(DefaultSizeStyle, 'l')

This method recursively applies the style to all shapes in the selection, including shapes nested inside groups. It only updates shapes that support the given style property.

Use Editor#setStyleForNextShapes to change the style for subsequently created shapes, and Editor#getStyleForNextShape to read it:

typescript
// Next shapes will be blue
editor.setStyleForNextShapes(DefaultColorStyle, 'blue')

// Create a new shape - it will be blue
editor.createShape({ type: 'geo', props: { w: 100, h: 100 } })

const nextColor = editor.getStyleForNextShape(DefaultColorStyle) // 'blue'

setStyleForSelectedShapes only updates the selected shapes; it does not change the value for next shapes. The style panel calls both methods when the user picks a value, so the change applies to the selection and carries over to the next shape. Do the same in your own code if you want that behavior:

typescript
editor.run(() => {
	editor.setStyleForSelectedShapes(DefaultColorStyle, 'blue')
	editor.setStyleForNextShapes(DefaultColorStyle, 'blue')
})

Default styles

The @tldraw/tlschema package provides a set of default style properties that the built-in shapes use. Colors reference theme values rather than raw hex codes, so shapes adapt to light and dark modes.

StyleValuesUsed for
DefaultColorStyleblack, red, blue, green, and other named colorsPrimary shape color
DefaultFillStylenone, semi, solid, pattern, fill, lined-fillFill pattern
DefaultDashStyledraw, solid, dashed, dotted, noneStroke style
DefaultSizeStyles, m, l, xlRelative size scale
DefaultFontStyledraw, sans, serif, monoFont family
DefaultTextAlignStylestart, middle, endHorizontal text alignment
DefaultHorizontalAlignStylestart, middle, endHorizontal content alignment within bounds
DefaultVerticalAlignStylestart, middle, endVertical content alignment within bounds
GeoShapeGeoStylerectangle, ellipse, triangle, and other geo typesGeometric shape type
ArrowShapeArrowheadStartStylearrow, triangle, dot, none, and other arrowhead typesStart arrowhead
ArrowShapeArrowheadEndStyleSame values as the start styleEnd arrowhead
ArrowShapeKindStylearc, elbowArrow routing
LineShapeSplineStyleline, cubicLine spline type

The shape-specific styles are defined in @tldraw/tlschema next to their shape's record type.

Opacity is not a StyleProp. It's a regular property on the base shape (TLBaseShape) that all shapes inherit, but it behaves like a style through its own methods: Editor#getSharedOpacity, Editor#setOpacityForSelectedShapes, and Editor#setOpacityForNextShapes.

Customizing default styles

Change the default value of any style with setDefaultValue. Enum styles also support addValues and removeValues for extending the built-in set at runtime:

typescript
import { DefaultSizeStyle } from 'tldraw'

DefaultSizeStyle.setDefaultValue('s')