Skip to main content

Overview

Excalidraw can be loaded directly in the browser using ES modules and a CDN. This approach is perfect for simple integrations, prototypes, or when you don’t want to use a build system.

Quick Start

Here’s the simplest way to get Excalidraw running:
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Excalidraw Example</title>
    
    <!-- Set asset path for fonts -->
    <script>
      window.EXCALIDRAW_ASSET_PATH = 
        "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
    </script>
    
    <style>
      body {
        margin: 0;
        padding: 0;
        font-family: sans-serif;
      }
      #root {
        height: 100vh;
        width: 100vw;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>

    <!-- Import Excalidraw from CDN -->
    <script type="module">
      import * as ExcalidrawLib from "https://esm.sh/@excalidraw/excalidraw";
      import React from "https://esm.sh/react@19";
      import ReactDOM from "https://esm.sh/react-dom@19/client";

      const { Excalidraw } = ExcalidrawLib;
      const root = ReactDOM.createRoot(document.getElementById("root"));
      
      root.render(
        React.createElement(Excalidraw)
      );
    </script>
  </body>
</html>
Replace @0.18.0 with the latest version from npm.

Project Setup with Build Tool

For better performance and development experience, use a build tool like Vite:
1

Initialize project

npm init -y
npm install vite react react-dom @excalidraw/excalidraw
2

Create HTML file

Create index.html:
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Excalidraw Example</title>
    <script>
      window.EXCALIDRAW_ASSET_PATH =
        "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="index.tsx"></script>
  </body>
</html>
3

Create entry file

Create index.tsx:
index.tsx
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";

const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <Excalidraw />
  </StrictMode>
);
4

Configure Vite

Create vite.config.mts:
vite.config.mts
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    port: 3000,
  },
});
5

Add scripts to package.json

package.json
{
  "name": "excalidraw-example",
  "scripts": {
    "start": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "@excalidraw/excalidraw": "*"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "typescript": "^5.0.0"
  }
}
6

Run development server

npm start
Open http://localhost:3000 in your browser.

Advanced Example with Features

Here’s a comprehensive example showing various Excalidraw features:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Excalidraw Advanced Example</title>
    <script>
      window.EXCALIDRAW_ASSET_PATH =
        "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
    </script>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="app">
      <div class="toolbar">
        <button id="load-scene">Load Demo Scene</button>
        <button id="reset-scene">Reset Scene</button>
        <button id="export-png">Export PNG</button>
        <label>
          <input type="checkbox" id="view-mode" /> View Mode
        </label>
        <label>
          <input type="checkbox" id="grid-mode" /> Grid Mode
        </label>
        <label>
          <input type="checkbox" id="dark-mode" /> Dark Mode
        </label>
      </div>
      <div id="root"></div>
    </div>
    <script type="module" src="index.tsx"></script>
  </body>
</html>

Using Initial Data

Load predefined elements when Excalidraw starts:
initialData.tsx
import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/data/transform";
import type { FileId } from "@excalidraw/excalidraw/element/types";

const elements: ExcalidrawElementSkeleton[] = [
  {
    type: "rectangle",
    x: 10,
    y: 10,
    strokeWidth: 2,
    id: "1",
  },
  {
    type: "diamond",
    x: 120,
    y: 20,
    backgroundColor: "#fff3bf",
    strokeWidth: 2,
    label: {
      text: "HELLO EXCALIDRAW",
      strokeColor: "#099268",
      fontSize: 30,
    },
    id: "2",
  },
  {
    type: "arrow",
    x: 100,
    y: 200,
    label: { text: "HELLO WORLD!!" },
    start: { type: "rectangle" },
    end: { type: "ellipse" },
  },
  {
    type: "frame",
    children: ["1", "2"],
    name: "My frame",
  },
];

export default {
  elements,
  appState: { 
    viewBackgroundColor: "#AFEEEE", 
    currentItemFontFamily: 5 
  },
  scrollToContent: true,
};
Use it in your app:
import initialData from "./initialData";
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";

const initialDataResolved = {
  ...initialData,
  elements: convertToExcalidrawElements(initialData.elements),
};

root.render(
  <Excalidraw initialData={initialDataResolved} />
);

Working with Images

Load and display images in your canvas:
import { useState, useEffect } from "react";
import { 
  Excalidraw,
  MIME_TYPES,
  convertToExcalidrawElements,
  type ExcalidrawImperativeAPI 
} from "@excalidraw/excalidraw";
import type { BinaryFileData, FileId } from "@excalidraw/excalidraw/types";

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

  useEffect(() => {
    if (!excalidrawAPI) return;

    const loadImage = async () => {
      const res = await fetch("/images/rocket.jpeg");
      const imageData = await res.blob();
      const reader = new FileReader();
      reader.readAsDataURL(imageData);

      reader.onload = function () {
        const imagesArray: BinaryFileData[] = [
          {
            id: "rocket" as FileId,
            dataURL: reader.result as string,
            mimeType: MIME_TYPES.jpg,
            created: Date.now(),
            lastRetrieved: Date.now(),
          },
        ];

        excalidrawAPI.addFiles(imagesArray);

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

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

    loadImage();
  }, [excalidrawAPI]);

  return (
    <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
  );
}

File Operations

Load and save Excalidraw files:
import { 
  loadSceneOrLibraryFromBlob,
  MIME_TYPES,
  type ExcalidrawImperativeAPI 
} from "@excalidraw/excalidraw";
import { fileOpen } from "browser-fs-access";

// Load scene from file
const loadSceneOrLibrary = async (
  excalidrawAPI: ExcalidrawImperativeAPI | null
) => {
  if (!excalidrawAPI) return;

  const file = await fileOpen({ 
    description: "Excalidraw or library file" 
  });
  
  const contents = await loadSceneOrLibraryFromBlob(file, null, null);
  
  if (contents.type === MIME_TYPES.excalidraw) {
    excalidrawAPI.updateScene(contents.data as any);
  } else if (contents.type === MIME_TYPES.excalidrawlib) {
    excalidrawAPI.updateLibrary({
      libraryItems: contents.data.libraryItems!,
      openLibraryMenu: true,
    });
  }
};

TypeScript Setup

Create tsconfig.json for TypeScript support:
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Production Build

1

Build for production

npm run build
This creates optimized files in the dist/ directory.
2

Preview production build

npm run preview
3

Deploy

Upload the dist/ directory to your hosting provider (Netlify, Vercel, GitHub Pages, etc.).

CDN Alternatives

Instead of esm.sh, you can use other CDNs:
<script type="module">
  import * as ExcalidrawLib from "https://unpkg.com/@excalidraw/excalidraw@latest/dist/excalidraw.production.min.js";
</script>

Live Examples

Explore these working examples:

Next Steps