Environment detection

The tldraw SDK provides two objects for detecting the user's environment: tlenv for fixed browser and platform information, and tlenvReactive for values that change during a session (like whether the user is using touch input). We use these internally to work around browser quirks, and you can use them in custom shapes or tools.

Static environment: tlenv

The tlenv object contains values detected at page load. These don't change during a session.

import { tlenv } from 'tldraw'

// Browser detection
tlenv.isSafari // true if Safari (excluding Chrome on iOS)
tlenv.isFirefox // true if Firefox
tlenv.isChromeForIos // true if Chrome running on iOS

// Platform detection
tlenv.isIos // true if iPad or iPhone
tlenv.isAndroid // true if Android device
tlenv.isDarwin // true if macOS

// Capability detection
tlenv.hasCanvasSupport // true if Promise and HTMLCanvasElement exist

Common patterns

Platform-specific keyboard shortcuts:

// Use Cmd on Mac, Ctrl elsewhere
const accelKey = tlenv.isDarwin ? e.metaKey : e.ctrlKey

Mobile detection:

const isMobile = tlenv.isIos || tlenv.isAndroid
if (isMobile) {
	// Adjust UI for mobile
}

Browser-specific workarounds:

// Safari needs extra time for SVG image export
if (tlenv.isSafari) {
	await new Promise((r) => setTimeout(r, 250))
}

Reactive environment: tlenvReactive

The tlenvReactive atom contains values that can change during a session, like the current pointer type. Use useValue to subscribe to changes in React components.

import { tlenvReactive, useValue } from 'tldraw'

function TouchFriendlyButton() {
	const { isCoarsePointer } = useValue(tlenvReactive)

	return (
		<button style={{ padding: isCoarsePointer ? 16 : 8 }}>
			{/* Larger touch target when using touch input */}
			Click me
		</button>
	)
}

Coarse pointer detection

The isCoarsePointer value tracks whether the user is currently using touch input. We detect this two ways: by listening to the (any-pointer: coarse) media query, and by checking pointerType on each pointer event. This dual approach handles devices that support both mouse and touch—like laptops with touchscreens—where the user might switch input methods mid-session.

import { tlenvReactive, react } from 'tldraw'

// Access the current value directly
const isCoarse = tlenvReactive.get().isCoarsePointer

// Subscribe to changes outside React
react('pointer type changed', () => {
	const { isCoarsePointer } = tlenvReactive.get()
	console.log('Coarse pointer:', isCoarsePointer)
})

Note: We force fine pointer mode on Firefox desktop regardless of the actual input device, since Firefox's coarse pointer reporting is unreliable there.

Browser quirks we handle

Here's a sample of what we use environment detection for internally.

Safari has the most workarounds. SVG-to-image export fails silently unless we add a 250ms delay after loading the image—a WebKit bug that's been open for years. We also draw minimap triangles four times because Safari's WebGL implementation sometimes drops draw calls. Text outlines render incorrectly, so we disable them entirely on Safari.

iOS coalesced pointer events are broken (they report wrong coordinates), so we skip them entirely and use individual events. We also handle the virtual keyboard differently since iOS doesn't fire standard resize events.

Firefox desktop's (any-pointer: coarse) media query reports false positives when a touchscreen is present but not in use. We force fine pointer mode on Firefox desktop to avoid jumpy UI.

Chrome for iOS has its own print implementation that doesn't trigger the standard beforeprint event, so we detect it and handle printing manually.

Prev
Embed shape
Next
Error handling