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:

CodeSandbox

Interactive browser-based example

GitHub Repository

View the complete source code

Next Steps

React Integration

Learn advanced React patterns

Next.js Integration

Integrate with Next.js applications

API Reference

Explore the complete API

Examples

Browse more examples