Back to Tldraw

User preferences

apps/docs/content/sdk-features/user-preferences.mdx

5.4.07.7 KB
Original Source

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

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

function PreferencesPanel() {
	const editor = useEditor()

	// Read preferences reactively
	const isDark = useValue('isDarkMode', () => editor.user.getIsDarkMode(), [editor])

	// 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 cover visual settings (color scheme, animation speed), interaction settings (snap mode, edge scroll speed), and identity (user name, color, locale). By default the editor stores them in localStorage and syncs them across tabs with a BroadcastChannel.

To supply preferences from your own auth or state instead, pass a user created with useTldrawCurrentUser (or createTLCurrentUser) to the Tldraw component. Preferences you provide this way are yours to persist and sync. See Collaboration for a full example.

Reading preferences

The UserPreferencesManager exposes each preference as a computed value. These are reactive signals: read them inside useValue (or another reactive context) and your component re-renders when the value changes.

tsx
// 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()

// The user's id as a `user:` prefixed record id, for presence and attribution
const userId = editor.user.getRecordId()

// All preferences as an object, with defaults resolved
const allPrefs = editor.user.getUserPreferences()

The tables below list the getter for each preference. Every preference in TLUserPreferences is optional; null or undefined means "use the default".

Updating preferences

Use UserPreferencesManager#updateUserPreferences to change one or more preferences at once:

tsx
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

PreferenceGetterTypeDefaultDescription
colorSchemegetIsDarkMode'light' | 'dark' | 'system''light'Theme mode
animationSpeedgetAnimationSpeednumber1, or 0 if the user prefers reduced motionMultiplier for animation durations
enhancedA11yModegetEnhancedA11yModebooleanfalseAdditional UI labels and visual aids

Interaction preferences

PreferenceGetterTypeDefaultDescription
isSnapModegetIsSnapModebooleanfalseSnap shapes to other shapes and guides
isWrapModegetIsWrapModebooleanfalseBrush selection only selects shapes fully inside the brush ("Select on wrap")
isDynamicSizeModegetIsDynamicResizeModebooleanfalseScale new shapes with the zoom level so they stay the same size on screen
isPasteAtCursorModegetIsPasteAtCursorModebooleanfalsePaste at cursor instead of original location
edgeScrollSpeedgetEdgeScrollSpeednumber1Speed multiplier for edge scrolling during drag
areKeyboardShortcutsEnabledgetAreKeyboardShortcutsEnabledbooleantrueEnable or disable keyboard shortcuts
inputModegetInputMode'trackpad' | 'mouse' | nullnullOptimize behavior for input device
isZoomDirectionInvertedgetIsZoomDirectionInvertedbooleanfalseInvert scroll-wheel zoom direction. Only applies when inputMode is 'mouse'

Identity properties

PreferenceGetterTypeDefaultDescription
idgetExternalId, getRecordIdstringAuto-generatedUnique user identifier. getRecordId returns it as a TLUserId
namegetNamestring''Display name shown to collaborators
colorgetColorstringRandom from the built-in paletteUser color for cursor and selections
localegetLocalestringBrowser localeLanguage code (e.g., 'en', 'fr')

The user color is picked at random from USER_COLORS, a palette of 12 colors. The id is what attribution and presence records use to identify the user.

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:

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

The editor-level colorScheme prop sets the default color scheme. When a user preference is set, it takes priority over the prop. See Themes for more.

Persistence and synchronization

The default user persists preferences to localStorage under the key TLDRAW_USER_DATA_v3. Each save includes a version number, and the editor migrates older data on load so preferences stay compatible across tldraw releases. It validates the loaded data with userTypeValidator and falls back to fresh preferences if the data is invalid.

The editor also broadcasts preference changes to other tabs over a BroadcastChannel. When you change a preference in one tab, all other tabs update automatically.

  • Toggle dark mode - Toggle between light and dark mode by changing colorScheme.