Basic Setup
Create your first canvas
Create a simple drawing canvas with minimal configuration:That’s it! You now have a fully functional drawing canvas with all of Excalidraw’s features.
App.tsx
Copy
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
export default function App() {
return (
<div style={{ height: "500px" }}>
<Excalidraw />
</div>
);
}
Working with the Canvas
Getting the API Reference
Access the Excalidraw API to programmatically control the canvas:Copy
import { useState } from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null);
return (
<div style={{ height: "500px" }}>
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
);
}
The API instance is passed to the
excalidrawAPI callback when the component mounts. Store it in state or a ref to use it throughout your component.Listening to Changes
Track when users modify the canvas using theonChange callback:
Copy
import { Excalidraw } from "@excalidraw/excalidraw";
import type {
NonDeletedExcalidrawElement,
AppState
} from "@excalidraw/excalidraw/types";
export default function App() {
const handleChange = (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState
) => {
console.log("Elements changed:", elements);
console.log("Canvas state:", {
zoom: appState.zoom,
scrollX: appState.scrollX,
scrollY: appState.scrollY,
viewBackgroundColor: appState.viewBackgroundColor,
});
};
return (
<div style={{ height: "500px" }}>
<Excalidraw onChange={handleChange} />
</div>
);
}
The
onChange callback fires frequently during drawing operations. Consider debouncing or throttling if you’re performing expensive operations like saving to a database.Loading Initial Data
Loading a Scene
Load predefined elements when the canvas initializes:Copy
import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
export default function App() {
const initialData = {
elements: convertToExcalidrawElements([
{
type: "rectangle",
x: 100,
y: 100,
strokeWidth: 2,
strokeColor: "#c92a2a",
backgroundColor: "#fff3bf",
width: 200,
height: 150,
},
{
type: "arrow",
x: 350,
y: 175,
label: { text: "Hello World!" },
start: { type: "rectangle" },
end: { type: "ellipse" },
},
{
type: "diamond",
x: 450,
y: 120,
backgroundColor: "#fff3bf",
strokeWidth: 2,
label: {
text: "HELLO EXCALIDRAW",
strokeColor: "#099268",
fontSize: 30,
},
},
]),
appState: {
viewBackgroundColor: "#AFEEEE",
zoom: { value: 1 },
},
scrollToContent: true,
};
return (
<div style={{ height: "500px" }}>
<Excalidraw initialData={initialData} />
</div>
);
}
Loading with Images
Load a scene that includes images:Copy
import { useEffect, useState } from "react";
import { Excalidraw, convertToExcalidrawElements, MIME_TYPES } from "@excalidraw/excalidraw";
import type {
ExcalidrawImperativeAPI,
BinaryFileData
} from "@excalidraw/excalidraw/types";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null);
useEffect(() => {
if (!excalidrawAPI) return;
const loadImage = async () => {
const res = await fetch("/path/to/image.jpg");
const imageData = await res.blob();
const reader = new FileReader();
reader.readAsDataURL(imageData);
reader.onload = () => {
const imageFile: BinaryFileData = {
id: "image-1" as BinaryFileData["id"],
dataURL: reader.result as BinaryFileData["dataURL"],
mimeType: MIME_TYPES.jpg,
created: Date.now(),
lastRetrieved: Date.now(),
};
excalidrawAPI.addFiles([imageFile]);
};
};
loadImage();
}, [excalidrawAPI]);
const initialData = {
elements: convertToExcalidrawElements([
{
type: "image",
x: 400,
y: 200,
width: 300,
height: 300,
fileId: "image-1",
},
]),
};
return (
<div style={{ height: "500px" }}>
<Excalidraw
excalidrawAPI={(api) => setExcalidrawAPI(api)}
initialData={initialData}
/>
</div>
);
}
Updating the Scene Programmatically
Adding New Elements
Add elements to the canvas using the API:Copy
import { useState } from "react";
import {
Excalidraw,
convertToExcalidrawElements,
restoreElements
} from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null);
const addRectangle = () => {
if (!excalidrawAPI) return;
const newElements = convertToExcalidrawElements([
{
type: "rectangle",
x: Math.random() * 400,
y: Math.random() * 300,
width: 150,
height: 100,
strokeColor: "#c92a2a",
backgroundColor: "#ffc9c9",
fillStyle: "hachure",
},
]);
excalidrawAPI.updateScene({
elements: [
...excalidrawAPI.getSceneElements(),
...restoreElements(newElements, null),
],
});
};
return (
<>
<button onClick={addRectangle} style={{ margin: "10px" }}>
Add Rectangle
</button>
<div style={{ height: "500px" }}>
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
</>
);
}
Changing Background Color
Update the canvas background color:Copy
const changeBackground = (color: string) => {
if (!excalidrawAPI) return;
excalidrawAPI.updateScene({
appState: {
viewBackgroundColor: color,
},
});
};
// Usage
<button onClick={() => changeBackground("#edf2ff")}>Blue Background</button>
<button onClick={() => changeBackground("#fff3bf")}>Yellow Background</button>
View Modes
Excalidraw supports different view modes for various use cases:Copy
import { useState } from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
export default function App() {
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
const [gridModeEnabled, setGridModeEnabled] = useState(false);
return (
<>
<div style={{ display: "flex", gap: "10px", padding: "10px" }}>
<label>
<input
type="checkbox"
checked={viewModeEnabled}
onChange={(e) => setViewModeEnabled(e.target.checked)}
/>
View Mode (Read-only)
</label>
<label>
<input
type="checkbox"
checked={zenModeEnabled}
onChange={(e) => setZenModeEnabled(e.target.checked)}
/>
Zen Mode (Minimal UI)
</label>
<label>
<input
type="checkbox"
checked={gridModeEnabled}
onChange={(e) => setGridModeEnabled(e.target.checked)}
/>
Grid Mode
</label>
</div>
<div style={{ height: "500px" }}>
<Excalidraw
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
/>
</div>
</>
);
}
View Mode
View Mode
Enables read-only mode. Users can view and pan/zoom the canvas but cannot edit elements.
Zen Mode
Zen Mode
Hides the UI and shows only the canvas for distraction-free viewing or presenting.
Grid Mode
Grid Mode
Displays a grid overlay on the canvas to help with alignment.
Theme Support
Switch between light and dark themes:Copy
import { useState } from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
import type { Theme } from "@excalidraw/excalidraw/element/types";
export default function App() {
const [theme, setTheme] = useState<Theme>("light");
return (
<>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle Theme (Current: {theme})
</button>
<div style={{ height: "500px" }}>
<Excalidraw theme={theme} />
</div>
</>
);
}
If you don’t set a theme prop, Excalidraw will show a theme toggle button in the UI. Setting the theme prop hides the toggle button and locks the theme to your specified value.
Exporting Drawings
Export to PNG
Export the current canvas as a PNG image:Copy
import { useState } from "react";
import { Excalidraw, exportToBlob } from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null);
const exportToPNG = async () => {
if (!excalidrawAPI) return;
const blob = await exportToBlob({
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
mimeType: "image/png",
});
// Download the image
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "excalidraw-drawing.png";
link.click();
URL.revokeObjectURL(url);
};
return (
<>
<button onClick={exportToPNG}>Export to PNG</button>
<div style={{ height: "500px" }}>
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
</>
);
}
Export to SVG
Export as a scalable SVG image:Copy
import { exportToSvg } from "@excalidraw/excalidraw";
const exportToSVG = async () => {
if (!excalidrawAPI) return;
const svg = await exportToSvg({
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
});
// Download SVG
const svgData = svg.outerHTML;
const blob = new Blob([svgData], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "excalidraw-drawing.svg";
link.click();
URL.revokeObjectURL(url);
};
Export to Clipboard
Copy the drawing to the clipboard:Copy
import { exportToClipboard } from "@excalidraw/excalidraw";
const copyToClipboard = async (type: "png" | "svg" | "json") => {
if (!excalidrawAPI) return;
await exportToClipboard({
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
type,
});
alert(`Copied to clipboard as ${type.toUpperCase()}`);
};
// Usage
<button onClick={() => copyToClipboard("png")}>Copy as PNG</button>
<button onClick={() => copyToClipboard("svg")}>Copy as SVG</button>
<button onClick={() => copyToClipboard("json")}>Copy as JSON</button>
Customizing the UI
Hiding UI Elements
Control which UI elements are visible:Copy
<Excalidraw
UIOptions={{
canvasActions: {
loadScene: false, // Hide "Load" button
export: false, // Hide export button
saveAsImage: false, // Hide "Save as image" button
clearCanvas: false, // Hide "Clear canvas" button
changeViewBackgroundColor: false, // Hide background color picker
},
tools: {
image: false, // Hide image tool from toolbar
},
}}
/>
Custom UI Components
Add custom UI elements using thechildren prop:
Copy
import { Excalidraw, WelcomeScreen, MainMenu, Footer } from "@excalidraw/excalidraw";
export default function App() {
return (
<div style={{ height: "500px" }}>
<Excalidraw>
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint>
Use the menu to access features!
</WelcomeScreen.Hints.MenuHint>
<WelcomeScreen.Hints.ToolbarHint>
Select a tool to start drawing
</WelcomeScreen.Hints.ToolbarHint>
</WelcomeScreen>
<MainMenu>
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.DefaultItems.Export />
<MainMenu.Separator />
<MainMenu.ItemCustom>
<button onClick={() => alert("Custom action!")}>
Custom Menu Item
</button>
</MainMenu.ItemCustom>
</MainMenu>
<Footer>
<div style={{ padding: "10px" }}>
Custom footer content
</div>
</Footer>
</Excalidraw>
</div>
);
}
Complete Example
Here’s a complete example combining all the concepts:Copy
import { useState, useEffect } from "react";
import {
Excalidraw,
convertToExcalidrawElements,
exportToBlob,
WelcomeScreen,
MainMenu,
} from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
import type {
ExcalidrawImperativeAPI,
NonDeletedExcalidrawElement,
AppState,
Theme,
} from "@excalidraw/excalidraw/types";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null);
const [theme, setTheme] = useState<Theme>("light");
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [gridModeEnabled, setGridModeEnabled] = useState(false);
// Initial data with some shapes
const initialData = {
elements: convertToExcalidrawElements([
{
type: "rectangle",
x: 50,
y: 50,
width: 200,
height: 100,
strokeColor: "#c92a2a",
backgroundColor: "#ffc9c9",
},
{
type: "arrow",
x: 300,
y: 100,
label: { text: "Start Here!" },
start: { type: "rectangle" },
end: { type: "ellipse" },
},
]),
appState: {
viewBackgroundColor: "#AFEEEE",
},
scrollToContent: true,
};
// Track changes
const handleChange = (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState
) => {
console.log(`Canvas has ${elements.length} elements`);
};
// Export function
const handleExport = async () => {
if (!excalidrawAPI) return;
const blob = await exportToBlob({
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
mimeType: "image/png",
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "my-drawing.png";
link.click();
URL.revokeObjectURL(url);
};
// Add a random shape
const addRandomShape = () => {
if (!excalidrawAPI) return;
const shapes = ["rectangle", "ellipse", "diamond"] as const;
const colors = ["#ffc9c9", "#b2f2bb", "#a5d8ff", "#fff3bf"];
const newElement = convertToExcalidrawElements([
{
type: shapes[Math.floor(Math.random() * shapes.length)],
x: Math.random() * 400,
y: Math.random() * 300,
width: 100 + Math.random() * 100,
height: 100 + Math.random() * 100,
strokeColor: "#000",
backgroundColor: colors[Math.floor(Math.random() * colors.length)],
},
]);
excalidrawAPI.updateScene({
elements: [...excalidrawAPI.getSceneElements(), ...newElement],
});
};
return (
<div>
{/* Controls */}
<div style={{ padding: "10px", borderBottom: "1px solid #ddd" }}>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme === "light" ? "🌙" : "☀️"} Toggle Theme
</button>
<button onClick={addRandomShape}>Add Random Shape</button>
<button onClick={handleExport}>Export PNG</button>
<button onClick={() => excalidrawAPI?.resetScene()}>Clear Canvas</button>
<label style={{ marginLeft: "10px" }}>
<input
type="checkbox"
checked={viewModeEnabled}
onChange={(e) => setViewModeEnabled(e.target.checked)}
/>
View Mode
</label>
<label style={{ marginLeft: "10px" }}>
<input
type="checkbox"
checked={gridModeEnabled}
onChange={(e) => setGridModeEnabled(e.target.checked)}
/>
Grid
</label>
</div>
{/* Canvas */}
<div style={{ height: "600px" }}>
<Excalidraw
excalidrawAPI={(api) => setExcalidrawAPI(api)}
initialData={initialData}
onChange={handleChange}
theme={theme}
viewModeEnabled={viewModeEnabled}
gridModeEnabled={gridModeEnabled}
name="My Drawing Canvas"
UIOptions={{
canvasActions: {
loadScene: false,
},
}}
>
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint>
Click the menu to access more features
</WelcomeScreen.Hints.MenuHint>
<WelcomeScreen.Hints.ToolbarHint>
Choose a tool to start creating
</WelcomeScreen.Hints.ToolbarHint>
</WelcomeScreen>
<MainMenu>
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.DefaultItems.Export />
<MainMenu.Separator />
<MainMenu.DefaultItems.Help />
</MainMenu>
</Excalidraw>
</div>
</div>
);
}
What’s Next?
API Reference
Explore all available props and configuration options
Event Handling
Learn about all event callbacks and their use cases
Custom UI
Build custom UI components and integrate them with Excalidraw
Collaboration
Add real-time collaboration to your canvas
Need Help? Check out our examples for more real-world use cases, or join our Discord community to ask questions.