Back to Tldraw

UI components

apps/docs/content/sdk-features/ui-components.mdx

5.4.015.8 KB
Original Source

The tldraw package includes a complete React-based UI: the menus, toolbars, panels, and dialogs that users interact with when creating and editing content. The UI is composed of named component slots that you can override or hide one at a time.

The UI connects to the editor through React hooks and context providers. Components update automatically when editor state changes, so you can replace individual parts of the interface without reimplementing the logic that connects UI actions to editor operations.

How it works

Component slot architecture

The UI divides the screen into distinct layout zones:

┌─────────────────────────────────────────────────────────┐
│                    Top Panel                             │
├────────────┬──────────────────────────┬─────────────────┤
│   Left     │         Canvas           │     Right       │
│   Panel    │                          │     Panel       │
├────────────┴──────────────────────────┴─────────────────┤
│                   Bottom Panel                           │
└─────────────────────────────────────────────────────────┘

The top zone contains the main menu, helper buttons (like "Back to content"), an empty top panel slot, and the share and style panels. The bottom zone houses navigation controls, the main toolbar with drawing tools, and the help menu (if you provide one). On desktop, the style panel appears in the top-right zone; on mobile it moves into a popover opened from the toolbar.

Each zone can host multiple components. The toolbar includes the tool selector, tool-specific options, and the tool lock button.

Context providers and state management

The UI establishes a hierarchy of React context providers. At the root, TldrawUiContextProvider coordinates the other providers and applies your overrides. Specialized providers handle translations, tooltips, dialogs, toasts, UI events, accessibility announcements, breakpoints for responsive behavior, and the component registry.

The actions and tools providers turn editor methods into UI actions with labels, icons, and keyboard shortcuts. When you click a toolbar button, the component calls an action from context, which invokes the editor method. The same action can be triggered from the toolbar, a menu, or a keyboard shortcut. See Actions for details.

Reactive UI updates

UI components read editor state through hooks like useEditor and useValue. These hooks use the editor's reactive signal system to re-render when relevant state changes. The style panel uses useRelevantStyles to decide which style controls to show for the current selection: select a different shape and the panel updates.

Key components

Component slots

The UI defines several component slots you can override or hide. The Toolbar holds the tool buttons. The TopPanel is an empty top-center slot with no default component; use it for your own UI like a document title or sync status. The StylePanel shows style controls for the selected shapes. The MenuPanel (top-left) groups the main menu, the page menu, and quick actions. The NavigationPanel (bottom-left) provides zoom controls and the minimap toggle. HelperButtons appear based on editor state, such as "Back to content" when the camera is far from shapes.

Each slot is optional. Pass null to hide a component, or pass your own React component to replace the default. A few slots have no default: TopPanel and HelpMenu are null unless you provide a component (use DefaultHelpMenu to opt in to the built-in help menu), and SharePanel and CursorChatBubble only render when collaboration UI is enabled.

Slot props

The UI portion of the components prop is shaped by TLUiComponents. Every key is optional: use null to hide that slot, or pass a React component. When a slot has a documented props type in the table below, import that type from tldraw and type your replacement as React.ComponentType<…> (or implement the matching props). When the props column says none, the SDK does not declare extra props for that slot beyond what a plain ComponentType allows.

SlotProps (import from tldraw)
ContextMenuTLUiContextMenuProps
ActionsMenuTLUiActionsMenuProps
HelpMenuTLUiHelpMenuProps
ZoomMenuTLUiZoomMenuProps
MainMenuTLUiMainMenuProps
Minimapnone
StylePanelTLUiStylePanelProps
PageMenunone
NavigationPanelnone
Toolbarnone
RichTextToolbarTLUiRichTextToolbarProps
ImageToolbarnone
VideoToolbarnone
KeyboardShortcutsDialogTLUiKeyboardShortcutsDialogProps
QuickActionsTLUiQuickActionsProps
HelperButtonsTLUiHelperButtonsProps
DebugPanelnone
DebugMenunone
MenuPanelnone
TopPanelnone
SharePanelnone
CursorChatBubblenone
Dialogsnone
Toastsnone
A11ynone
FollowingIndicatornone
PeopleMenunone (default component: optional children via DefaultPeopleMenuProps)
PeopleMenuAvatarTLUiPeopleMenuAvatarProps
PeopleMenuFacePileTLUiPeopleMenuFacePileProps
PeopleMenuItemTLUiPeopleMenuItemProps
UserPresenceEditornone

This table is an index; the authoritative list and types remain TLUiComponents in the API reference and in the package typings.

UI hooks

Components access editor functionality through specialized hooks.

useEditor returns the editor instance, with direct access to all editor methods and state.

useActions returns the UI actions (copy, paste, delete, and so on) with their labels, icons, and keyboard shortcuts. Call an action's onSelect from your custom UI.

useTools returns the available tools with their metadata. The toolbar uses this to render tool buttons.

useRelevantStyles returns the styles relevant to the current selection and their values. It powers the style panel.

useBreakpoint returns a numeric breakpoint index (0-7) matching the PORTRAIT_BREAKPOINT constants. Compare against values like PORTRAIT_BREAKPOINT.MOBILE or PORTRAIT_BREAKPOINT.TABLET_SM to adapt layout for different screen sizes.

Hiding the UI

You can hide the default tldraw user interface entirely using the hideUi prop. This hides the visual UI only: keyboard shortcuts and clipboard handling keep working.

tsx
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw hideUi />
		</div>
	)
}

With the UI hidden, you can still control the editor programmatically through Editor methods. Open the console and try:

ts
editor.setCurrentTool('draw')

All of tldraw's user interface works by controlling the editor via its methods. If you hide the user interface, you can still use these same methods to control the editor. See the custom user interface example for this in action.

Extension points

Overriding components

Override individual components by passing them to the components prop:

tsx
import { Tldraw, useEditor, useTools } from 'tldraw'
import 'tldraw/tldraw.css'

function CustomToolbar() {
	const editor = useEditor()
	const tools = useTools()

	return (
		<div className="my-toolbar">
			{Object.values(tools).map((tool) => (
				<button key={tool.id} onClick={() => editor.setCurrentTool(tool.id)}>
					{tool.label}
				</button>
			))}
		</div>
	)
}

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw
				components={{
					Toolbar: CustomToolbar,
				}}
			/>
		</div>
	)
}

The useTools hook returns an object mapping tool IDs to TLUiToolItem objects. Each tool item contains metadata like id, label, icon, and kbd (keyboard shortcut).

Hiding components

Pass null to hide a component entirely. This is useful for focused experiences that don't need the full default UI:

tsx
<Tldraw
	components={{
		PageMenu: null,
		DebugMenu: null,
		NavigationPanel: null,
	}}
/>

See the UI components hidden example for a complete list of hideable components.

Overrides

Control tldraw's actions, tools, and translations with the overrides prop. This prop accepts a TLUiOverrides object, which has methods for actions and tools, and a translations property.

Actions

The user interface has a set of shared actions used in the menus and keyboard shortcuts. Override these by providing an actions method that receives the editor, the default actions, and a helpers object, then returns a mutated actions object. See Actions for the full story.

tsx
import { Tldraw, TLUiOverrides } from 'tldraw'
import 'tldraw/tldraw.css'

const myOverrides: TLUiOverrides = {
	actions(editor, actions, helpers) {
		// Delete an action (remember to also delete any menu items that reference it)
		delete actions['insert-embed']

		// Create a new action or replace an existing one
		actions['my-new-action'] = {
			id: 'my-new-action',
			label: 'My new action',
			readonlyOk: true,
			kbd: 'cmd+shift+u,ctrl+shift+u',
			onSelect(source) {
				window.alert('My new action just happened!')
			},
		}
		return actions
	},
}

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw overrides={myOverrides} />
		</div>
	)
}

The actions object is a map of TLUiActionItems, keyed by their id. See the action overrides example for more.

Tools

Override tools the same way you override actions. Provide a tools method that accepts the editor, the default tools object, and a helpers object, then returns a mutated version.

tsx
const myOverrides: TLUiOverrides = {
	tools(editor, tools, helpers) {
		// Create a tool item in the UI's context
		tools.card = {
			id: 'card',
			icon: 'geo-rectangle',
			label: 'tools.card',
			kbd: 'c',
			onSelect: () => {
				editor.setCurrentTool('card')
			},
		}
		return tools
	},
}

The tools object is a map of TLUiToolItems, keyed by their id. See the add tool to toolbar example for a complete implementation.

Translations

The translations property accepts a table of new translations. If you add a tool with label: 'tools.card', you need to provide an English translation for that key:

tsx
const myOverrides: TLUiOverrides = {
	translations: {
		en: {
			'tools.card': 'Card',
		},
	},
}

See internationalization for more about tldraw's translation system.

UI events

The Tldraw component has an onUiEvent prop that fires when users interact with the UI:

tsx
import { Tldraw, TLUiEventHandler } from 'tldraw'
import 'tldraw/tldraw.css'

export default function App() {
	const handleUiEvent: TLUiEventHandler = (name, data) => {
		console.log('UI event:', name, data)
	}

	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw onUiEvent={handleUiEvent} />
		</div>
	)
}

The callback receives the event name as a string and an object with the event's source (e.g. menu or context-menu) and other data specific to each event, such as the operation in an align-shapes event.

Note that onUiEvent only fires for UI interactions. Calling Editor#alignShapes directly won't trigger this callback. See the UI events example for more.

  • UI primitives - Use tldraw's button, menu, dialog, and other UI components in your custom interfaces
  • Overlay utils - Customize canvas overlays like brushes, indicators, snaps, scribbles, and collaborator cursors
  • Internationalization - Customize translations and add new languages
  • Tools - Learn how tools work and create your own