React Sketch Canvas
Guides

Backgrounds

Draw on top of an image background.

A background image turns the canvas into an annotation surface: mark up a photo, trace over a template, or take notes on top of an existing asset. The image stays separate from the user's strokes, so you can preserve the source and export the combined result later.

URL image

Pass any image URL to backgroundImage. Use preserveBackgroundImageAspectRatio to control how the image fits the canvas; the same values accepted by SVG's preserveAspectRatio attribute work here.

Presets:
App.tsx
import { Image } from "lucide-react";import { type ChangeEvent, useState } from "react";import { ReactSketchCanvas } from "react-sketch-canvas";const somePreserveAspectRatio = [	"none",	"xMinYMin",	"xMidYMin",	"xMaxYMin",	"xMinYMid",	"xMidYMid",	"xMaxYMid",	"xMinYMax",	"xMidYMax",	"xMaxYMax",] as const;type SomePreserveAspectRatio = (typeof somePreserveAspectRatio)[number];const PRESETS = [	{		name: "Chalkboard",		url: "https://images.pexels.com/photos/164005/pexels-photo-164005.jpeg",	},	{		name: "Wood Grain",		url: "https://images.pexels.com/photos/172277/pexels-photo-172277.jpeg",	},	{		name: "Crumpled Paper",		url: "https://images.pexels.com/photos/1103970/pexels-photo-1103970.jpeg",	},];export default function App() {	const [backgroundImage, setBackgroundImage] = useState(		"https://images.pexels.com/photos/164005/pexels-photo-164005.jpeg",	);	const [preserveAspectRatio, setPreserveAspectRatio] =		useState<SomePreserveAspectRatio>("none");	const handlePreserveAspectRatioChange = (		event: ChangeEvent<HTMLSelectElement>,	) => {		setPreserveAspectRatio(event.target.value as SomePreserveAspectRatio);	};	const handleBackgroundImageChange = (		event: ChangeEvent<HTMLInputElement>,	) => {		setBackgroundImage(event.target.value);	};	return (		<div className="not-prose flex flex-col gap-4 w-full">			{/* Settings Panel */}			<div className="flex flex-col md:flex-row gap-4 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground">				{/* Image URL Input & Presets */}				<div className="flex flex-col gap-2 flex-1">					<label						className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"						htmlFor="backgroundImage"					>						<Image className="w-3.5 h-3.5" />						Background Image URL					</label>					<input						type="text"						id="backgroundImage"						placeholder="URL or Data URI of background image"						value={backgroundImage}						onChange={handleBackgroundImageChange}						className="h-9 w-full rounded-md border border-fd-border bg-fd-muted px-3 text-sm focus:outline-none focus:ring-2 focus:ring-fd-ring transition-all"					/>					{/* Quick Preset Buttons */}					<div className="flex flex-wrap gap-1.5 items-center mt-1">						<span className="text-[10px] uppercase font-bold text-fd-muted-foreground mr-1">							Presets:						</span>						{PRESETS.map((preset) => (							<button								key={preset.name}								type="button"								onClick={() => setBackgroundImage(preset.url)}								className={`px-2.5 py-0.5 rounded border text-[11px] font-medium transition-colors duration-150 ${									backgroundImage === preset.url										? "border-fd-primary bg-fd-primary/10 text-fd-primary"										: "border-fd-border bg-fd-card text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent/50"								}`}							>								{preset.name}							</button>						))}					</div>				</div>				{/* Aspect Ratio Selector */}				<div className="flex flex-col gap-2 min-w-[14rem]">					<label						className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"						htmlFor="preserveAspectRatio"					>						{/* aspect ratio icon equivalent */}						<Image className="w-3.5 h-3.5 text-fd-primary" />						Aspect Ratio					</label>					<select						id="preserveAspectRatio"						value={preserveAspectRatio}						onChange={handlePreserveAspectRatioChange}						className="h-9 rounded-md border border-fd-border bg-fd-muted px-3 text-sm focus:outline-none focus:ring-2 focus:ring-fd-ring transition-all"					>						{somePreserveAspectRatio.map((value) => (							<option key={value} value={value}>								{value}							</option>						))}					</select>				</div>			</div>			{/* Canvas Workspace */}			<div className="relative overflow-hidden rounded-lg border border-fd-border aspect-video min-h-[240px] shadow-sm">				<ReactSketchCanvas					backgroundImage={backgroundImage}					preserveBackgroundImageAspectRatio={preserveAspectRatio}					strokeColor="var(--color-fd-primary)"					canvasColor="transparent"				/>			</div>		</div>	);}

Data URI

The same prop also accepts a data URI. This is useful when the image lives in memory: an uploaded file, an API response, or a generated asset. The canvas API stays the same; only the shape of the value changes.

Presets:
App.tsx
import { Image } from "lucide-react";import { type ChangeEvent, useState } from "react";import { ReactSketchCanvas } from "react-sketch-canvas";const somePreserveAspectRatio = [	"none",	"xMinYMin",	"xMidYMin",	"xMaxYMin",	"xMinYMid",	"xMidYMid",	"xMaxYMid",	"xMinYMax",	"xMidYMax",	"xMaxYMax",] as const;const roseSquarePngDataUri =	"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJUlEQVR4nGP4s3Tpf1pihlELRi0YtWDUglELRi0YtWDUgqFhAQDbxFuqIVYqkAAAAABJRU5ErkJggg==";const blueCirclePngDataUri =	"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAUklEQVR4nO3NywkAIAxEwfR/tSbLEm1AYzYfIZKFvb4h+n6tj8k9LGyC0DiEaOMixBq/IqGAV/yIFFBAAsAT2cafAB4IG7ciorgWgeIIpA6n2QI8UFEt+WgukgAAAABJRU5ErkJggg==";const purpleRectPngDataUri =	"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJklEQVR4nGNgGAWjgGrgQMuf/9TEoxaMWjBqwagF5FgwCkYB2QAAlIoLPraPCW0AAAAASUVORK5CYII=";type SomePreserveAspectRatio = (typeof somePreserveAspectRatio)[number];const PRESETS = [	{ name: "Rose Square (24x24)", uri: roseSquarePngDataUri },	{ name: "Blue Circle (24x24)", uri: blueCirclePngDataUri },	{ name: "Purple Rectangle (24x24)", uri: purpleRectPngDataUri },];export default function App() {	const [bgDataURI, setBgDataURI] = useState(blueCirclePngDataUri);	const [preserveAspectRatio, setPreserveAspectRatio] =		useState<SomePreserveAspectRatio>("xMidYMid");	const handlePreserveAspectRatioChange = (		event: ChangeEvent<HTMLSelectElement>,	) => {		setPreserveAspectRatio(event.target.value as SomePreserveAspectRatio);	};	const handleBackgroundImageChange = (		event: ChangeEvent<HTMLInputElement>,	) => {		setBgDataURI(event.target.value);	};	return (		<div className="not-prose flex flex-col gap-4 w-full">			{/* Settings Panel */}			<div className="flex flex-col md:flex-row gap-4 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground">				{/* Data URI Input & Presets */}				<div className="flex flex-col gap-2 flex-1">					<label						className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"						htmlFor="bgDataURI"					>						<Image className="w-3.5 h-3.5" />						Background Data URI					</label>					<input						type="text"						id="bgDataURI"						placeholder="Data URI of the image to use as a background"						value={bgDataURI}						onChange={handleBackgroundImageChange}						className="h-9 w-full rounded-md border border-fd-border bg-fd-muted px-3 text-sm focus:outline-none focus:ring-2 focus:ring-fd-ring transition-all"					/>					{/* Predefined Presets */}					<div className="flex flex-wrap gap-1.5 items-center mt-1">						<span className="text-[10px] uppercase font-bold text-fd-muted-foreground mr-1">							Presets:						</span>						{PRESETS.map((preset) => (							<button								key={preset.name}								type="button"								onClick={() => setBgDataURI(preset.uri)}								className={`px-2.5 py-0.5 rounded border text-[11px] font-medium transition-colors duration-150 ${									bgDataURI === preset.uri										? "border-fd-primary bg-fd-primary/10 text-fd-primary"										: "border-fd-border bg-fd-card text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent/50"								}`}							>								{preset.name}							</button>						))}					</div>				</div>				{/* Aspect Ratio Selector */}				<div className="flex flex-col gap-2 min-w-[14rem]">					<label						className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"						htmlFor="preserveAspectRatio"					>						<Image className="w-3.5 h-3.5 text-fd-primary" />						Aspect Ratio					</label>					<select						id="preserveAspectRatio"						value={preserveAspectRatio}						onChange={handlePreserveAspectRatioChange}						className="h-9 rounded-md border border-fd-border bg-fd-muted px-3 text-sm focus:outline-none focus:ring-2 focus:ring-fd-ring transition-all"					>						{somePreserveAspectRatio.map((value) => (							<option key={value} value={value}>								{value}							</option>						))}					</select>				</div>			</div>			{/* Canvas Workspace */}			<div className="relative overflow-hidden rounded-lg border border-fd-border aspect-video min-h-[240px] shadow-sm">				<ReactSketchCanvas					backgroundImage={bgDataURI}					preserveBackgroundImageAspectRatio={preserveAspectRatio}					strokeColor="var(--color-fd-primary)"					canvasColor="transparent"				/>			</div>		</div>	);}

backgroundImage is treated as trusted. The library loads it directly into an SVG <image> element, and for SVG data URIs it is parsed with DOMParser to read the embedded viewBox. Do not pass attacker-controlled strings here without validating them first.

See also

On this page