Skip to main content

Overview

Excalidraw can be directly embedded as a React component in your application. This guide demonstrates how to set up and use Excalidraw with React, including advanced features like custom UI, state management, and export functionality.

Installation

1

Install dependencies

Install Excalidraw and its peer dependencies using npm or yarn:
npm install react react-dom @excalidraw/excalidraw
# or
yarn add react react-dom @excalidraw/excalidraw
To use unreleased changes, install @excalidraw/excalidraw@next
2

Import styles

Import the Excalidraw CSS in your component:
import "@excalidraw/excalidraw/index.css";
3

Configure asset path (optional)

For self-hosting fonts, set the asset path before rendering:
window.EXCALIDRAW_ASSET_PATH = "/";
Copy fonts from node_modules/@excalidraw/excalidraw/dist/prod/fonts to your public directory.

Basic Usage

Here’s a minimal React component using Excalidraw:
App.tsx
import React from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";

export default function App() {
  return (
    <div style={{ height: "100vh", width: "100vw" }}>
      <Excalidraw />
    </div>
  );
}
Excalidraw takes 100% of its container’s width and height. Ensure the parent element has non-zero dimensions.

Advanced Integration

Setting up the API Reference

Access Excalidraw’s imperative API to control the editor programmatically:
import React, { useState } from "react";
import { Excalidraw, type ExcalidrawImperativeAPI } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";

export default function App() {
  const [excalidrawAPI, setExcalidrawAPI] = 
    useState<ExcalidrawImperativeAPI | null>(null);

  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
    </div>
  );
}

Loading Initial Data

Load elements and configure the initial state:
import React from "react";
import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
import type { ExcalidrawInitialDataState } from "@excalidraw/excalidraw/types";

const initialData: ExcalidrawInitialDataState = {
  elements: convertToExcalidrawElements([
    {
      type: "rectangle",
      x: 10,
      y: 10,
      strokeWidth: 2,
      id: "rect-1",
    },
    {
      type: "diamond",
      x: 120,
      y: 20,
      backgroundColor: "#fff3bf",
      strokeWidth: 2,
      label: {
        text: "HELLO EXCALIDRAW",
        strokeColor: "#099268",
        fontSize: 30,
      },
      id: "diamond-1",
    },
    {
      type: "arrow",
      x: 100,
      y: 200,
      label: { text: "HELLO WORLD!!" },
      start: { type: "rectangle" },
      end: { type: "ellipse" },
    },
  ]),
  appState: { 
    viewBackgroundColor: "#AFEEEE",
    currentItemFontFamily: 5 
  },
  scrollToContent: true,
};

export default function App() {
  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw initialData={initialData} />
    </div>
  );
}

Event Handlers

import React from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
import type { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import type { AppState } from "@excalidraw/excalidraw/types";

export default function App() {
  const handleChange = (
    elements: NonDeletedExcalidrawElement[],
    state: AppState
  ) => {
    console.log("Elements:", elements);
    console.log("App state:", state);
  };

  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw onChange={handleChange} />
    </div>
  );
}

Custom UI Components

Excalidraw provides composable UI components for customization:
import React, { useState } from "react";
import { 
  Excalidraw,
  Footer,
  WelcomeScreen,
  MainMenu,
  Sidebar
} from "@excalidraw/excalidraw";

export default function App() {
  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw>
        <WelcomeScreen />
        
        <MainMenu>
          <MainMenu.DefaultItems.SaveAsImage />
          <MainMenu.DefaultItems.Export />
          <MainMenu.Separator />
          <MainMenu.DefaultItems.Help />
          <MainMenu.ItemCustom>
            <button onClick={() => alert("Custom action!")}>
              Custom Menu Item
            </button>
          </MainMenu.ItemCustom>
        </MainMenu>

        <Sidebar name="custom">
          <Sidebar.Tabs>
            <Sidebar.Header />
            <Sidebar.Tab tab="one">Content for tab one</Sidebar.Tab>
            <Sidebar.Tab tab="two">Content for tab two</Sidebar.Tab>
            <Sidebar.TabTriggers>
              <Sidebar.TabTrigger tab="one">Tab 1</Sidebar.TabTrigger>
              <Sidebar.TabTrigger tab="two">Tab 2</Sidebar.TabTrigger>
            </Sidebar.TabTriggers>
          </Sidebar.Tabs>
        </Sidebar>

        <Footer>
          <div style={{ padding: "10px" }}>
            Custom footer content
          </div>
        </Footer>
      </Excalidraw>
    </div>
  );
}

Export Functionality

Export drawings to various formats:
import React, { useState } from "react";
import { 
  Excalidraw,
  exportToBlob,
  type ExcalidrawImperativeAPI 
} from "@excalidraw/excalidraw";

export default function App() {
  const [excalidrawAPI, setExcalidrawAPI] = 
    useState<ExcalidrawImperativeAPI | null>(null);

  const exportToPNG = async () => {
    if (!excalidrawAPI) return;

    const blob = await exportToBlob({
      elements: excalidrawAPI.getSceneElements(),
      mimeType: "image/png",
      appState: excalidrawAPI.getAppState(),
      files: excalidrawAPI.getFiles(),
    });

    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = "drawing.png";
    link.click();
  };

  return (
    <>
      <button onClick={exportToPNG}>Export to PNG</button>
      <div style={{ height: "calc(100vh - 40px)" }}>
        <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
      </div>
    </>
  );
}

Configuration Options

Customize Excalidraw’s behavior with props:
import React from "react";
import { Excalidraw } from "@excalidraw/excalidraw";

export default function App() {
  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw
        viewModeEnabled={false}
        zenModeEnabled={false}
        gridModeEnabled={true}
        renderScrollbars={true}
        theme="light"
        name="My Drawing"
        UIOptions={{
          canvasActions: {
            loadScene: false,
          },
          tools: { 
            image: true 
          },
        }}
        validateEmbeddable={true}
      />
    </div>
  );
}

Working with Images

Add binary files (images) to your scene:
import React, { useEffect, useState } from "react";
import { 
  Excalidraw,
  MIME_TYPES,
  convertToExcalidrawElements,
  type ExcalidrawImperativeAPI 
} from "@excalidraw/excalidraw";
import type { BinaryFileData, FileId } 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 = function () {
        const imageFile: BinaryFileData = {
          id: "image-1" as FileId,
          dataURL: reader.result as string,
          mimeType: MIME_TYPES.jpg,
          created: Date.now(),
          lastRetrieved: Date.now(),
        };

        excalidrawAPI.addFiles([imageFile]);

        // Add image element to scene
        const imageElement = convertToExcalidrawElements([{
          type: "image",
          x: 100,
          y: 100,
          width: 200,
          height: 200,
          fileId: "image-1" as FileId,
        }]);

        excalidrawAPI.updateScene({
          elements: [
            ...excalidrawAPI.getSceneElements(),
            ...imageElement,
          ],
        });
      };
    };

    loadImage();
  }, [excalidrawAPI]);

  return (
    <div style={{ height: "100vh" }}>
      <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
    </div>
  );
}

Scene Management

Update and manipulate the scene:
import React, { useState } from "react";
import { 
  Excalidraw,
  convertToExcalidrawElements,
  restoreElements,
  ROUNDNESS,
  type ExcalidrawImperativeAPI 
} from "@excalidraw/excalidraw";

export default function App() {
  const [excalidrawAPI, setExcalidrawAPI] = 
    useState<ExcalidrawImperativeAPI | null>(null);

  const updateScene = () => {
    if (!excalidrawAPI) return;

    const sceneData = {
      elements: restoreElements(
        convertToExcalidrawElements([
          {
            type: "rectangle",
            id: "rect-1",
            fillStyle: "hachure",
            strokeWidth: 1,
            strokeStyle: "solid",
            roughness: 1,
            angle: 0,
            x: 100,
            y: 100,
            strokeColor: "#c92a2a",
            width: 186,
            height: 141,
            roundness: {
              type: ROUNDNESS.ADAPTIVE_RADIUS,
              value: 32,
            },
          },
          {
            type: "text",
            x: 300,
            y: 100,
            text: "HELLO WORLD!",
          },
        ]),
        null
      ),
      appState: {
        viewBackgroundColor: "#edf2ff",
      },
    };

    excalidrawAPI.updateScene(sceneData);
  };

  const resetScene = () => {
    excalidrawAPI?.resetScene();
  };

  return (
    <>
      <button onClick={updateScene}>Update Scene</button>
      <button onClick={resetScene}>Reset Scene</button>
      <div style={{ height: "calc(100vh - 40px)" }}>
        <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
      </div>
    </>
  );
}

Live Example

Check out the complete working example on CodeSandbox.

Next Steps

API Reference

Explore all available methods and props

Next.js Integration

Learn how to use Excalidraw with Next.js

TypeScript Types

View TypeScript type definitions

Examples

Browse more integration examples