The user interface in tldraw includes the menus, toolbars, keyboard shortcuts, and analytics events in the editor.

Hiding the UI

You can hide the default tldraw user interface entirely using the hideUi prop. This turns off both the visuals as well as the keyboard shortcuts.

function Example() {
	return <Tldraw hideUi />
}

Here's an example of what that looks like. Note that while you can't select any other tools using the keyboard shortcuts, you can still use the setCurrentTool method to change the tool. If you open the console and enter:

editor.setCurrentTool('draw')

...then you can start drawing.

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

The source for these examples are available in the tldraw repository or in a sandbox here.

Overrides

The content of tldraw's menus can be controlled via the overrides prop. This prop accepts a TLUiOverrides object, which has methods for each part of the user interface, such as the toolbar or keyboardShortcutsMenu.

Actions

The user interface has a set of shared actions that are used in the menus and keyboard shortcuts. These actions can be overridden by passing a new set of actions to the overrides.actions method.

To create, update, or delete actions, provide an actions method that receives both the editor and the default actions and returns a mutated actions object.

const myOverrides: TLUiOverrides = {
	actions(editor, actions) {
		// You can delete actions, but remember to
		// also delete the menu items that reference them!
		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: '$u',
			onSelect(source: any) {
				// Whatever you want to happen when the action is run
				window.alert('My new action just happened!')
			},
		}
		return actions
	},
}

The actions object is a map of TLUiActionItems, with each item keyed under its id.

Tools

Tools work in the same manner as actions. You can override the default tools by passing a tools method that accepts the default tools object and returns a mutated version of that object.

const myOverrides: TLUiOverrides = {
	tools(editor, tools) {
		// Create a tool item in the ui's context.
		tools.card = {
			id: 'card',
			icon: 'color',
			label: 'tools.card',
			kbd: 'c',
			readonlyOk: false,
			onSelect: () => {
				// Whatever you want to happen when the tool is selected
				editor.setCurrentTool('card')
			},
		}
		return tools
	},
}

The tools object is a map of TLUiToolItems, with each item keyed under its id.

Toolbar and Menus

The remaining overrides are for toolbar and the various menus: the main menu, actions menu, context menu, help menu, and the keyboard shortcuts menu.

Each of these overrides accepts a method that receives the default menu schema and returns a mutated version of that schema.

const myOverrides: TLUiOverrides = {
	actions(editor, actions) {
		// Create a new action or replace an existing one
		actions['my-new-action'] = {
			id: 'my-new-action',
			label: 'My new action',
			readonlyOk: true,
			kbd: '$u',
			onSelect(source: any) {
				window.alert('My new action just happened!')
			},
		}
		return actions
	},
	contextMenu(editor, contextMenu, { actions }) {
		const newMenuGroup = menuGroup('my-items', newMenuItem)
		contextMenu.unshift(newMenuItem)
		return contextMenu
	},
	menu(editor, menu, { actions }) {
		// using the findMenuItem helper
		const fileMenu = findMenuItem(menu, ['menu', 'file'])
		if (fileMenu.type === 'submenu') {
			// add the new item to the file menu's children
			const newMenuItem = menuItem(actions['my-new-action'])
			fileMenu.children.unshift(newMenuItem)
		}
		return menu
	},
}

A menu schema is an array of either submenus, groups, items, or custom items. Each group or submenu may include any of the other types as its children.

The menu schema is stateful. Referencing atomic properties (such as computed values in the editor) will cause the menu to update when those values change. If you wish for a menu item to disappear from the menu, you can return null from the menu method. You can also provide additional options for each item, disabled or checked.

const myOverrides: TLUiOverrides = {
	menu(editor, menu, { actions }) {
		const selectedShapes = editor.getSelectedShapeIds().length

		const newMenuGroup = menuGroup(
			'my-actions',
			selectedShapes > 0 ? menuItem(actions['action-a']) : null,
			menuItem(actions['action-b'], { disabled: selectedShapes < 3 })
		)

		menu.unshift(newMenuGroup)

		return menu
	},
}

It's recommmended to explore the default menu schema in order to understand how menu items work.

Translations

The translations method accepts a table of new translations. For example, if you wanted a tool to reference a key "tools.card", then you should at minimum provide an english translation for this key.

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

Events

The Tldraw component has a prop, onUiEvent, that the user interface will call when certain events occur.

function Example() {
	function handleEvent(name, data) {
		// do something with the event
	}

	return <Tldraw onUiEvent={handleEvent} />
}

The onUiEvent callback is called with the name of the event as a string and an object with information about the event's source (e.g. menu or context-menu) and possibly other data specific to each event, such as the direction in an align-shapes event.

Note that onUiEvent is only called when interacting with the user interface. It is not called when running commands manually against the app, e.g. calling Editor.alignShapes will not call onUiEvent.

See the tldraw repository for an example of how to customize tldraw's user interface.

PersistenceCollaboration