User preferences

User preferences store per-user settings that persist across sessions and synchronize across browser tabs. Access them through editor.user:

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

function PreferencesPanel() {
	const editor = useEditor()

	// Read preferences
	const isDark = editor.user.getIsDarkMode()
	const animationSpeed = editor.user.getAnimationSpeed()
	const locale = editor.user.getLocale()

	// Update preferences
	const toggleDarkMode = () => {
		editor.user.updateUserPreferences({
			colorScheme: isDark ? 'light' : 'dark',
		})
	}

	return <button onClick={toggleDarkMode}>Toggle theme</button>
}

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

Preferences fall into three categories: visual settings (color scheme, animation speed), interaction settings (snap mode, edge scroll speed), and identity properties (user name, color, locale). The system stores data in localStorage and uses the BroadcastChannel API to sync changes across tabs in real time.

Reading preferences

The UserPreferencesManager exposes each preference as a computed value. These are reactive: when you read them, your component automatically re-renders when the value changes.

// Individual preferences
const isDark = editor.user.getIsDarkMode()
const speed = editor.user.getAnimationSpeed()
const locale = editor.user.getLocale()
const userName = editor.user.getName()
const userColor = editor.user.getColor()
const isSnapMode = editor.user.getIsSnapMode()

// All preferences as an object
const allPrefs = editor.user.getUserPreferences()

Updating preferences

Use updateUserPreferences() to change one or more preferences at once:

editor.user.updateUserPreferences({
	colorScheme: 'dark',
	animationSpeed: 0.5,
	isSnapMode: true,
})

Changes apply immediately, save to localStorage, and broadcast to other tabs.

Available preferences

Visual preferences

PreferenceTypeDefaultDescription
colorScheme'light' | 'dark' | 'system''light'Theme mode
animationSpeednumber1 (or 0 if reduced motion)Multiplier for animation durations
enhancedA11yModebooleanfalseAdditional UI labels and visual aids

When colorScheme is 'system', the editor tracks the operating system's dark mode preference through a media query listener.

Interaction preferences

PreferenceTypeDefaultDescription
isSnapModebooleanfalseSnap shapes to other shapes and guides
isWrapModebooleanfalseEnable text wrapping in text shapes
isDynamicSizeModebooleanfalseLive shape updates during resize
isPasteAtCursorModebooleanfalsePaste at cursor instead of original location
edgeScrollSpeednumber1Speed multiplier for edge scrolling during drag
areKeyboardShortcutsEnabledbooleantrueEnable or disable keyboard shortcuts
inputMode'trackpad' | 'mouse' | nullnullOptimize behavior for input device

Identity properties

PreferenceTypeDefaultDescription
idstringAuto-generatedUnique user identifier
namestring''Display name shown to collaborators
colorstringRandom from paletteUser color for cursor and selections
localestringBrowser localeLanguage code (e.g., 'en', 'fr')

The user color is randomly chosen from 12 visually distinct colors designed for collaboration.

Dark mode

The getIsDarkMode() method resolves the color scheme to a boolean. When colorScheme is 'system', it tracks the operating system's preference through a media query listener:

const isDark = editor.user.getIsDarkMode()
// true if colorScheme is 'dark', or 'system' with OS in dark mode

You can also use the inferDarkMode prop on the Tldraw component to automatically infer the initial theme from the user's system preference:

<Tldraw inferDarkMode />

Persistence and synchronization

Preferences persist to localStorage under the key TLDRAW_USER_DATA_v3. Each save includes a version number, and the system runs migrations when loading older data to keep preferences compatible across tldraw releases.

The system uses the BroadcastChannel API to sync preference changes across browser tabs. When you change a preference in one tab, all other tabs update automatically. Each tab has a unique origin ID to avoid processing its own broadcasts.

Accessibility defaults

The animationSpeed default respects the prefers-reduced-motion media query. Users with reduced motion enabled get animationSpeed: 0 by default, disabling animations without manual configuration.

Validation

Preferences are validated using userTypeValidator from @tldraw/editor. Invalid data falls back to fresh preferences rather than causing errors.

  • Toggle dark mode - Toggle between light and dark mode by changing colorScheme.
  • Infer dark mode - Automatically infer the initial theme from the user's system preference.
Prev
User following
Next
Validation