React Sketch Canvas
Guides

Exporting & saving

Export drawings as PNG, JPEG, or SVG, or save them as paths to load later.

The canvas can produce three kinds of output: a raster image (PNG or JPEG), an SVG string, and the underlying path data. Pick the one that matches what you need to store, share, or restore.

Raster export (PNG, JPEG)

Call exportImage("png") or exportImage("jpeg"). The method returns a Promise<string> that resolves to a data URL.

Export to PNG
export function () {
  const  = <ReactSketchCanvasRef>(null);

  return (
    <>
      < ={} />
      <
        ="button"
        ={async () => {
          const  = await .?.("png"); 
          if () .();
        }}
      >
        Export PNG
      </>
    </>
  );
}

SVG export

exportSvg() returns a Promise<string> with the SVG markup. Use this when you want a vector output you can embed elsewhere, or process server-side.

const  = await .?.();

Save and load

To persist a drawing and restore it later, work with the path data directly. exportPaths() returns the array of CanvasPath objects the canvas is rendering. loadPaths() accepts that same shape.

Round-trip save and load
export function () {
  const  = <ReactSketchCanvasRef>(null);

  const  = async () => {
    const  = (await .?.()) ?? []; 
    .("sketch", .());
  };

  const  = () => {
    const  = .("sketch");
    if (!) return;
    const  = .() as CanvasPath[];
    .?.(); 
  };

  return (
    <>
      < ={} />
      < ="button" ={}>Save</>
      < ="button" ={}>Load</>
    </>
  );
}

loadPaths() appends to the existing strokes. Call resetCanvas() first if you want to replace the current drawing instead of adding to it.

Exporting with a background

Set exportWithBackgroundImage on the canvas to include the configured backgroundImage in raster exports. Use this when consumers need a PNG or JPEG that shows both the drawing and the background.

Export Configurator
Interactive Canvas
App.tsx
import {	Check,	Copy,	Download,	ExternalLink,	Image,	Settings,	Trash2,} from "lucide-react";import { type ChangeEvent, useEffect, useRef, useState } from "react";import {	type CanvasPath,	ReactSketchCanvas,	type ReactSketchCanvasRef,} from "react-sketch-canvas";const backgroundSources = {	remote:		"https://images.pexels.com/photos/1193743/pexels-photo-1193743.jpeg?cs=srgb&fm=jpg",	dataUri:		"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 360'%3E%3Crect width='640' height='360' fill='%23f8fafc'/%3E%3Cpath d='M0 260 C120 170 220 310 340 220 S540 160 640 230 V360 H0 Z' fill='%23bfdbfe'/%3E%3Ccircle cx='500' cy='95' r='54' fill='%23facc15'/%3E%3Cpath d='M60 110 H300' stroke='%2314b8a6' stroke-width='20' stroke-linecap='round'/%3E%3Cpath d='M60 150 H230' stroke='%23fb7185' stroke-width='20' stroke-linecap='round'/%3E%3C/svg%3E",	inaccessible: "https://example.invalid/react-sketch-canvas-background.png",} as const;const initialPaths: CanvasPath[] = [	{		drawMode: true,		strokeColor: "#dc2626",		strokeWidth: 5,		paths: [			{ x: 80, y: 80 },			{ x: 140, y: 130 },			{ x: 210, y: 95 },			{ x: 280, y: 150 },		],	},	{		drawMode: true,		strokeColor: "#2563eb",		strokeWidth: 8,		paths: [			{ x: 360, y: 95 },			{ x: 420, y: 145 },			{ x: 500, y: 105 },			{ x: 560, y: 155 },		],	},];type BackgroundSource = keyof typeof backgroundSources;type ExportFormat = "png" | "jpeg" | "svg";type ExportResult =	| { format: "png" | "jpeg"; value: string }	| { format: "svg"; value: string };export default function App() {	const canvasRef = useRef<ReactSketchCanvasRef>(null);	const [backgroundSource, setBackgroundSource] =		useState<BackgroundSource>("dataUri");	const [exportFormat, setExportFormat] = useState<ExportFormat>("png");	const [exportWithBackgroundImage, setExportWithBackgroundImage] =		useState(true);	const [exportAtFixedSize, setExportAtFixedSize] = useState(false);	const [exportResult, setExportResult] = useState<ExportResult | null>(null);	const [svgViewerUrl, setSvgViewerUrl] = useState("");	const [copied, setCopied] = useState(false);	useEffect(() => {		if (!copied) return;		const timer = setTimeout(() => setCopied(false), 2000);		return () => clearTimeout(timer);	}, [copied]);	useEffect(() => {		canvasRef.current?.resetCanvas();		canvasRef.current?.loadPaths(initialPaths);	}, []);	useEffect(() => {		if (exportResult?.format !== "svg") {			setSvgViewerUrl("");			return;		}		const url = URL.createObjectURL(			new Blob([exportResult.value], { type: "image/svg+xml" }),		);		setSvgViewerUrl(url);		return () => URL.revokeObjectURL(url);	}, [exportResult]);	const handleBackgroundSourceChange = (		event: ChangeEvent<HTMLSelectElement>,	) => {		setBackgroundSource(event.target.value as BackgroundSource);		setExportResult(null);	};	const handleExportFormatChange = (event: ChangeEvent<HTMLSelectElement>) => {		setExportFormat(event.target.value as ExportFormat);		setExportResult(null);	};	const handleExportWithBackgroundChange = (		event: ChangeEvent<HTMLInputElement>,	) => {		setExportWithBackgroundImage(event.target.checked);		setExportResult(null);	};	const handleFixedSizeChange = (event: ChangeEvent<HTMLInputElement>) => {		setExportAtFixedSize(event.target.checked);		setExportResult(null);	};	const handleExportClick = async () => {		if (!canvasRef.current) return;		if (exportFormat === "svg") {			const svg = await canvasRef.current.exportSvg();			setExportResult({ format: "svg", value: svg });			return;		}		const image = await canvasRef.current.exportImage(			exportFormat,			exportAtFixedSize ? { width: 320, height: 180 } : undefined,		);		setExportResult({ format: exportFormat, value: image });	};	const handleClearOutputClick = () => {		setExportResult(null);	};	const handleCopySvgClick = async () => {		if (exportResult?.format !== "svg") return;		await navigator.clipboard.writeText(exportResult.value);		setCopied(true);	};	const backgroundImage = backgroundSources[backgroundSource];	const showFallbackNote =		backgroundSource === "inaccessible" &&		exportWithBackgroundImage &&		exportFormat !== "svg";	return (		<div className="not-prose flex flex-col gap-5 w-full">			{/* Grid Configuration and Canvas */}			<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">				{/* Configurations Panel */}				<div className="lg:col-span-1 flex flex-col gap-4 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground h-fit">					<span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground border-b border-fd-border/50 pb-2">						<Settings className="w-3.5 h-3.5 text-fd-primary" />						Export Configurator					</span>					{/* Background Source */}					<div className="flex flex-col gap-1.5">						<label							htmlFor="backgroundSource"							className="text-xs font-medium text-fd-muted-foreground"						>							Background Layer						</label>						<select							id="backgroundSource"							value={backgroundSource}							onChange={handleBackgroundSourceChange}							className="h-9 rounded-md border border-fd-border bg-fd-muted px-2.5 text-xs font-medium focus:ring-2 focus:ring-fd-ring outline-none"						>							<option value="remote">Remote image URL</option>							<option value="dataUri">Data URI</option>							<option value="inaccessible">Inaccessible URL</option>						</select>					</div>					{/* Export Format */}					<div className="flex flex-col gap-1.5">						<label							htmlFor="exportFormat"							className="text-xs font-medium text-fd-muted-foreground"						>							File Format						</label>						<select							id="exportFormat"							value={exportFormat}							onChange={handleExportFormatChange}							className="h-9 rounded-md border border-fd-border bg-fd-muted px-2.5 text-xs font-medium focus:ring-2 focus:ring-fd-ring outline-none"						>							<option value="png">PNG (Raster)</option>							<option value="jpeg">JPEG (Raster)</option>							<option value="svg">SVG (Vector)</option>						</select>					</div>					{/* Toggles */}					<div className="flex flex-col gap-2.5 mt-1 border-t border-fd-border/30 pt-3">						<label className="flex items-center gap-2 cursor-pointer group text-xs text-fd-foreground">							<input								id="exportWithBackgroundImage"								type="checkbox"								checked={exportWithBackgroundImage}								onChange={handleExportWithBackgroundChange}								className="w-3.5 h-3.5 accent-fd-primary rounded cursor-pointer"							/>							<span className="group-hover:text-fd-primary transition-colors">								Include background image							</span>						</label>						<label							className={`flex items-center gap-2 cursor-pointer group text-xs text-fd-foreground transition-opacity ${								exportFormat === "svg"									? "opacity-30 pointer-events-none"									: "opacity-100"							}`}						>							<input								id="exportAtFixedSize"								type="checkbox"								checked={exportAtFixedSize}								disabled={exportFormat === "svg"}								onChange={handleFixedSizeChange}								className="w-3.5 h-3.5 accent-fd-primary rounded cursor-pointer"							/>							<span className="group-hover:text-fd-primary transition-colors">								Export raster as 320 x 180							</span>						</label>					</div>					{/* Action Triggers */}					<div className="flex gap-2 mt-2 pt-3 border-t border-fd-border/30">						<button							type="button"							onClick={handleExportClick}							className="flex-1 inline-flex h-9 items-center justify-center gap-1.5 rounded-md bg-fd-primary px-3 text-xs font-semibold text-fd-primary-foreground shadow transition-colors hover:bg-fd-primary/90"						>							<Download className="w-3.5 h-3.5" />							Export						</button>						<button							type="button"							onClick={handleClearOutputClick}							className="inline-flex h-9 items-center justify-center p-2 rounded-md border border-fd-border bg-fd-card text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent shadow-sm transition-colors"							title="Clear Output"						>							<Trash2 className="w-3.5 h-3.5" />						</button>					</div>					{showFallbackNote && (						<p className="p-2 rounded bg-amber-500/10 border border-amber-500/20 text-[10px] text-amber-600 dark:text-amber-500 mt-2 font-medium leading-relaxed">							Note: If background fails to load during export, the drawing will							still render without the background layer.						</p>					)}				</div>				{/* Canvas Workspace */}				<div className="lg:col-span-2 flex flex-col gap-2">					<span className="text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground">						Interactive Canvas					</span>					<div className="relative overflow-hidden rounded-lg border border-fd-border bg-fd-card h-[280px] shadow-sm">						<ReactSketchCanvas							ref={canvasRef}							backgroundImage={backgroundImage}							canvasColor="#f8fafc"							exportWithBackgroundImage={exportWithBackgroundImage}							preserveBackgroundImageAspectRatio="xMidYMid slice"							strokeWidth={5}							strokeColor="#111827"						/>					</div>				</div>			</div>			{/* Conditionally Rendered Output Panel */}			{exportResult && (				<div className="relative flex flex-col gap-3 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground">					{/* Toast Notification */}					{copied && (						<div className="absolute top-4 right-4 flex items-center gap-1.5 rounded-md bg-emerald-500/10 dark:bg-emerald-500/20 px-2.5 py-1 text-xs font-semibold text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 dark:border-emerald-500/35 shadow-sm animate-in fade-in slide-in-from-top-1 duration-200">							<Check className="w-3.5 h-3.5 text-emerald-500" />							Copied to clipboard!						</div>					)}					<span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground border-b border-fd-border/50 pb-2">						<Image className="w-3.5 h-3.5 text-fd-primary" />						Export Output ({exportResult.format.toUpperCase()})					</span>					{exportResult.format === "svg" ? (						<div className="flex flex-col gap-3">							<div className="flex flex-wrap gap-2">								<button									type="button"									onClick={handleCopySvgClick}									className="inline-flex h-8 items-center gap-1.5 rounded-md border border-fd-border bg-fd-card px-3 text-xs font-medium hover:bg-fd-accent transition-colors shadow-sm"								>									<Copy className="w-3 h-3" />									Copy SVG Code								</button>								{svgViewerUrl && (									<a										href={svgViewerUrl}										target="_blank"										rel="noreferrer"										className="inline-flex h-8 items-center gap-1.5 rounded-md border border-fd-border bg-fd-card px-3 text-xs font-medium hover:bg-fd-accent transition-colors shadow-sm"									>										<ExternalLink className="w-3 h-3" />										Open in New Tab									</a>								)}							</div>							<textarea								readOnly								rows={6}								value={exportResult.value}								className="w-full rounded-md border border-fd-border bg-fd-muted p-3 font-mono text-xs text-fd-foreground focus:outline-none"							/>						</div>					) : (						<div className="flex flex-col gap-2 max-w-lg">							<div className="overflow-hidden rounded border border-fd-border shadow-inner bg-fd-muted">								<img									src={exportResult.value}									alt={`${exportResult.format.toUpperCase()} export preview`}									className="w-full h-auto object-contain max-h-[220px]"								/>							</div>							<span className="text-[10px] text-fd-muted-foreground text-center">								Right click image to download or copy raster output							</span>						</div>					)}				</div>			)}		</div>	);}

Raster export with a remote background can fail at runtime. The export rejects with a distinct error for each failure mode: cross-origin (CORS), data URI decode, and same-origin "not reachable". Catch the rejection and show an appropriate fallback to the user.

JPEG exports always paint canvasColor as the base because JPEG has no transparent pixels. When preserveBackgroundImageAspectRatio letterboxes the background, the letterbox regions in a JPEG export will be canvasColor. PNG exports keep those regions transparent unless the background fully covers the canvas.

Raster quality and pixel ratio

exportImage() accepts an optional { width, height } second argument. With no second argument, the output is scaled by window.devicePixelRatio so it stays sharp on high-DPI screens. With explicit dimensions, the result is exactly that many pixels, which is what you want for fixed-size assets like thumbnails, social cards, or print previews.

Quality Configurator
Projected Dimensions:1280 x 720 px
Interactive Canvas (640 x 360)
App.tsx
import {	Download,	Image as ImageIcon,	Info,	Settings,	Trash2,} from "lucide-react";import { type ChangeEvent, useEffect, useRef, useState } from "react";import {	type CanvasPath,	ReactSketchCanvas,	type ReactSketchCanvasRef,} from "react-sketch-canvas";const CANVAS_WIDTH = 640;const CANVAS_HEIGHT = 360;const initialPaths: CanvasPath[] = [	{		drawMode: true,		strokeColor: "#0f172a",		strokeWidth: 6,		paths: [			{ x: 60, y: 250 },			{ x: 120, y: 130 },			{ x: 200, y: 220 },			{ x: 280, y: 90 },			{ x: 360, y: 200 },			{ x: 440, y: 70 },			{ x: 540, y: 220 },			{ x: 600, y: 130 },		],	},	{		drawMode: true,		strokeColor: "#ef4444",		strokeWidth: 3,		paths: [			{ x: 90, y: 90 },			{ x: 140, y: 95 },			{ x: 190, y: 92 },		],	},];type ExportFormat = "png" | "jpeg";type ExportResult = {	format: ExportFormat;	value: string;	logicalWidth: number;	logicalHeight: number;	pixelWidth: number;	pixelHeight: number;	byteLength: number;};function estimateDataUrlBytes(dataUrl: string): number {	const commaIndex = dataUrl.indexOf(",");	if (commaIndex === -1) return 0;	const base64 = dataUrl.slice(commaIndex + 1);	const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;	return Math.max(0, Math.floor((base64.length * 3) / 4) - padding);}function formatBytes(bytes: number): string {	if (bytes < 1024) return `${bytes} B`;	if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;	return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;}export default function App() {	const canvasRef = useRef<ReactSketchCanvasRef>(null);	const [exportFormat, setExportFormat] = useState<ExportFormat>("png");	const [exportWidth, setExportWidth] = useState<number>(CANVAS_WIDTH);	const [exportHeight, setExportHeight] = useState<number>(CANVAS_HEIGHT);	const [pixelRatio, setPixelRatio] = useState<number>(2);	const [exportAtExplicitSize, setExportAtExplicitSize] =		useState<boolean>(true);	const [exportResult, setExportResult] = useState<ExportResult | null>(null);	const [deviceDpr, setDeviceDpr] = useState<number>(1);	useEffect(() => {		canvasRef.current?.resetCanvas();		canvasRef.current?.loadPaths(initialPaths);		if (typeof window !== "undefined") {			setDeviceDpr(window.devicePixelRatio || 1);		}	}, []);	const handleExportFormatChange = (event: ChangeEvent<HTMLSelectElement>) => {		setExportFormat(event.target.value as ExportFormat);		setExportResult(null);	};	const handleExportWidthChange = (event: ChangeEvent<HTMLInputElement>) => {		setExportWidth(Number.parseInt(event.target.value, 10) || 0);		setExportResult(null);	};	const handleExportHeightChange = (event: ChangeEvent<HTMLInputElement>) => {		setExportHeight(Number.parseInt(event.target.value, 10) || 0);		setExportResult(null);	};	const handlePixelRatioChange = (event: ChangeEvent<HTMLSelectElement>) => {		setPixelRatio(Number.parseFloat(event.target.value));		setExportResult(null);	};	const handleUseExplicitSizeChange = (		event: ChangeEvent<HTMLInputElement>,	) => {		setExportAtExplicitSize(event.target.checked);		setExportResult(null);	};	const handleExportClick = async () => {		if (!canvasRef.current) return;		const logicalWidth = exportAtExplicitSize ? exportWidth : CANVAS_WIDTH;		const logicalHeight = exportAtExplicitSize ? exportHeight : CANVAS_HEIGHT;		const effectiveRatio = exportAtExplicitSize ? pixelRatio : deviceDpr;		const pixelWidth = Math.round(logicalWidth * effectiveRatio);		const pixelHeight = Math.round(logicalHeight * effectiveRatio);		const image = exportAtExplicitSize			? await canvasRef.current.exportImage(exportFormat, {					width: pixelWidth,					height: pixelHeight,				})			: await canvasRef.current.exportImage(exportFormat);		setExportResult({			format: exportFormat,			value: image,			logicalWidth,			logicalHeight,			pixelWidth,			pixelHeight,			byteLength: estimateDataUrlBytes(image),		});	};	const handleClearOutputClick = () => {		setExportResult(null);	};	const projectedPixelWidth = exportAtExplicitSize		? Math.round(exportWidth * pixelRatio)		: Math.round(CANVAS_WIDTH * deviceDpr);	const projectedPixelHeight = exportAtExplicitSize		? Math.round(exportHeight * pixelRatio)		: Math.round(CANVAS_HEIGHT * deviceDpr);	return (		<div className="not-prose flex flex-col gap-5 w-full">			{/* Grid Layout Config & Canvas */}			<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">				{/* Configurations Panel */}				<div className="lg:col-span-1 flex flex-col gap-4 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground h-fit">					<span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground border-b border-fd-border/50 pb-2">						<Settings className="w-3.5 h-3.5 text-fd-primary" />						Quality Configurator					</span>					{/* Export Format */}					<div className="flex flex-col gap-1.5">						<label							htmlFor="rasterExportFormat"							className="text-xs font-medium text-fd-muted-foreground"						>							Format						</label>						<select							id="rasterExportFormat"							value={exportFormat}							onChange={handleExportFormatChange}							className="h-9 rounded-md border border-fd-border bg-fd-muted px-2.5 text-xs font-medium focus:ring-2 focus:ring-fd-ring outline-none"						>							<option value="png">PNG</option>							<option value="jpeg">JPEG</option>						</select>					</div>					{/* Dimensions inputs */}					<div className="grid grid-cols-2 gap-3">						<div className="flex flex-col gap-1.5">							<label								htmlFor="rasterExportWidth"								className="text-xs font-medium text-fd-muted-foreground"							>								Width (CSS px)							</label>							<input								id="rasterExportWidth"								type="number"								min={1}								max={4096}								step={20}								value={exportWidth}								disabled={!exportAtExplicitSize}								onChange={handleExportWidthChange}								className="h-9 rounded-md border border-fd-border bg-fd-muted px-3 text-xs font-mono focus:ring-2 focus:ring-fd-ring outline-none disabled:opacity-40 disabled:cursor-not-allowed"							/>						</div>						<div className="flex flex-col gap-1.5">							<label								htmlFor="rasterExportHeight"								className="text-xs font-medium text-fd-muted-foreground"							>								Height (CSS px)							</label>							<input								id="rasterExportHeight"								type="number"								min={1}								max={4096}								step={20}								value={exportHeight}								disabled={!exportAtExplicitSize}								onChange={handleExportHeightChange}								className="h-9 rounded-md border border-fd-border bg-fd-muted px-3 text-xs font-mono focus:ring-2 focus:ring-fd-ring outline-none disabled:opacity-40 disabled:cursor-not-allowed"							/>						</div>					</div>					{/* Pixel ratio dropdown */}					<div className="flex flex-col gap-1.5">						<label							htmlFor="rasterExportPixelRatio"							className="text-xs font-medium text-fd-muted-foreground"						>							Pixel Ratio						</label>						<select							id="rasterExportPixelRatio"							value={pixelRatio}							disabled={!exportAtExplicitSize}							onChange={handlePixelRatioChange}							className="h-9 rounded-md border border-fd-border bg-fd-muted px-2.5 text-xs font-medium focus:ring-2 focus:ring-fd-ring outline-none disabled:opacity-40 disabled:cursor-not-allowed"						>							<option value={1}>1x (Standard)</option>							<option value={2}>2x (Retina)</option>							<option value={3}>3x (Super Retina)</option>							<option value={4}>4x (Print Quality)</option>						</select>					</div>					{/* Toggle switch explicit options */}					<div className="flex flex-col gap-2 border-t border-fd-border/30 pt-3">						<label className="flex items-start gap-2.5 cursor-pointer group text-xs text-fd-foreground leading-normal">							<input								id="rasterExportUseExplicitSize"								type="checkbox"								checked={exportAtExplicitSize}								onChange={handleUseExplicitSizeChange}								className="w-3.5 h-3.5 accent-fd-primary rounded cursor-pointer mt-0.5"							/>							<span className="group-hover:text-fd-primary transition-colors">								Pass explicit <code>width</code> / <code>height</code>{" "}								properties (otherwise defaults to scaled device dpr ={" "}								{deviceDpr.toFixed(2)})							</span>						</label>					</div>					{/* Action Buttons */}					<div className="flex gap-2 mt-2 pt-3 border-t border-fd-border/30">						<button							type="button"							onClick={handleExportClick}							className="flex-1 inline-flex h-9 items-center justify-center gap-1.5 rounded-md bg-fd-primary px-3 text-xs font-semibold text-fd-primary-foreground shadow transition-colors hover:bg-fd-primary/90"						>							<Download className="w-3.5 h-3.5" />							Export Raster						</button>						<button							type="button"							onClick={handleClearOutputClick}							className="inline-flex h-9 items-center justify-center p-2 rounded-md border border-fd-border bg-fd-card text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent shadow-sm transition-colors"							title="Clear Output"						>							<Trash2 className="w-3.5 h-3.5" />						</button>					</div>					{/* Projected Stats Info Card */}					<div className="flex gap-2 p-2.5 rounded bg-fd-muted border border-fd-border/50 text-[11px] text-fd-muted-foreground mt-2 leading-relaxed">						<Info className="w-3.5 h-3.5 text-fd-primary flex-shrink-0 mt-0.5" />						<div>							<span>Projected Dimensions:</span>							<strong className="text-fd-foreground font-mono block">								{projectedPixelWidth} x {projectedPixelHeight} px							</strong>						</div>					</div>				</div>				{/* Canvas Workspace */}				<div className="lg:col-span-2 flex flex-col gap-2">					<span className="text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground">						Interactive Canvas ({CANVAS_WIDTH} x {CANVAS_HEIGHT})					</span>					<div className="relative overflow-hidden rounded-lg border border-fd-border bg-fd-card shadow-sm h-[280px]">						<div className="absolute inset-0 flex items-center justify-center bg-[#f8fafc]">							<ReactSketchCanvas								ref={canvasRef}								width="100%"								height="100%"								canvasColor="#f8fafc"								strokeColor="#0f172a"								strokeWidth={6}							/>						</div>					</div>				</div>			</div>			{/* Conditionally Rendered Output Panel */}			{exportResult && (				<div className="flex flex-col gap-3 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground">					<span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground border-b border-fd-border/50 pb-2">						<ImageIcon className="w-3.5 h-3.5 text-fd-primary" />						Raster Studio Output Preview					</span>					<div className="flex flex-col md:flex-row md:items-start gap-4">						{/* Stats Card */}						<div className="flex flex-col gap-2 p-3 bg-fd-muted rounded-md border border-fd-border min-w-[14rem] text-xs">							<div className="flex flex-col border-b border-fd-border/50 pb-1.5">								<span className="text-[10px] text-fd-muted-foreground uppercase font-bold">									Format								</span>								<span className="font-bold font-mono uppercase text-sm mt-0.5">									{exportResult.format}								</span>							</div>							<div className="flex flex-col border-b border-fd-border/50 pb-1.5">								<span className="text-[10px] text-fd-muted-foreground uppercase font-bold">									Export size								</span>								<span className="font-mono font-bold mt-0.5">									{exportResult.pixelWidth} x {exportResult.pixelHeight} px								</span>								<span className="text-[10px] text-fd-muted-foreground mt-0.5">									Logical: {exportResult.logicalWidth} x{" "}									{exportResult.logicalHeight} CSS px								</span>							</div>							<div className="flex flex-col">								<span className="text-[10px] text-fd-muted-foreground uppercase font-bold">									File size								</span>								<span className="font-mono font-bold mt-0.5">									{formatBytes(exportResult.byteLength)}								</span>							</div>						</div>						{/* Image */}						<div className="flex-1 flex flex-col gap-1.5 max-w-xl">							<div className="overflow-hidden rounded border border-fd-border bg-fd-muted shadow-inner">								<img									src={exportResult.value}									alt={`${exportResult.format.toUpperCase()} export preview`}									className="w-full h-auto object-contain max-h-[260px]"								/>							</div>							<span className="text-[10px] text-fd-muted-foreground text-center">								Right-click thumbnail to copy or save high-fidelity raster image							</span>						</div>					</div>				</div>			)}		</div>	);}

A common pattern for "crisp, fixed logical size" output is to multiply your logical CSS dimensions by a target pixel ratio:

const  = 640;
const  = 360;
const  = 2;

const  = await .?.("png", {
  :  * ,
  :  * ,
});

On this page