Skip to main content
This guide will help you integrate Excalidraw into your application and start drawing in just 5 minutes.

Basic Setup

1

Install Excalidraw

npm install react react-dom @excalidraw/excalidraw
2

Create your first canvas

Create a simple drawing canvas with minimal configuration:
App.tsx
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";

export default function App() {
  return (
    <div style={{ height: "500px" }}>
      <Excalidraw />
    </div>
  );
}
That’s it! You now have a fully functional drawing canvas with all of Excalidraw’s features.

Working with the Canvas

Getting the API Reference

Access the Excalidraw API to programmatically control the canvas:
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 the onChange callback:
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:
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:
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:
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:
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:
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>
    </>
  );
}
Enables read-only mode. Users can view and pan/zoom the canvas but cannot edit elements.
Hides the UI and shows only the canvas for distraction-free viewing or presenting.
Displays a grid overlay on the canvas to help with alignment.

Theme Support

Switch between light and dark themes:
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:
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:
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:
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:
<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 the children prop:
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:
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?

Need Help? Check out our examples for more real-world use cases, or join our Discord community to ask questions.