Overview
Excalidraw is designed to be easily embedded in React applications. You can integrate the full editor, customize its appearance, control its behavior, and interact with it programmatically.
Basic Embedding
Installation
First, install Excalidraw in your React project:
npm install @excalidraw/excalidraw
# or
yarn add @excalidraw/excalidraw
Simple Integration
Embed Excalidraw with minimal configuration:
import { Excalidraw } from "@excalidraw/excalidraw" ;
function App () {
return (
< div style = { { height: "100vh" } } >
< Excalidraw />
</ div >
);
}
export default App ;
Excalidraw requires a defined height. Make sure its parent container has a height set, either through CSS or inline styles.
Component Props
Essential Props
From packages/excalidraw/index.tsx:23-59, the main props available:
onChange
initialData
excalidrawAPI
import { Excalidraw } from "@excalidraw/excalidraw" ;
function App () {
const handleChange = ( elements , appState , files ) => {
console . log ( "Elements:" , elements );
console . log ( "AppState:" , appState );
console . log ( "Files:" , files );
};
return < Excalidraw onChange = { handleChange } /> ;
}
API Reference
ExcalidrawAPI Methods
Once you have the API reference, you can control Excalidraw programmatically:
import { Excalidraw } from "@excalidraw/excalidraw" ;
import { useState } from "react" ;
function App () {
const [ excalidrawAPI , setExcalidrawAPI ] = useState ( null );
const handleExcalidrawAPI = ( api ) => {
setExcalidrawAPI ( api );
};
// Get current scene elements
const getElements = () => {
const elements = excalidrawAPI ?. getSceneElements ();
console . log ( elements );
};
// Get app state
const getState = () => {
const appState = excalidrawAPI ?. getAppState ();
console . log ( appState );
};
// Get files
const getFiles = () => {
const files = excalidrawAPI ?. getFiles ();
console . log ( files );
};
// Update scene
const updateScene = () => {
excalidrawAPI ?. updateScene ({
elements: [
{
type: "text" ,
x: 100 ,
y: 100 ,
text: "Hello from API!" ,
fontSize: 20 ,
},
],
});
};
// Reset scene
const resetScene = () => {
excalidrawAPI ?. resetScene ();
};
// Scroll to content
const scrollToContent = () => {
excalidrawAPI ?. scrollToContent ();
};
return (
< div style = { { height: "100vh" , display: "flex" , flexDirection: "column" } } >
< div style = { { padding: "10px" } } >
< button onClick = { getElements } > Get Elements </ button >
< button onClick = { getState } > Get State </ button >
< button onClick = { getFiles } > Get Files </ button >
< button onClick = { updateScene } > Update Scene </ button >
< button onClick = { resetScene } > Reset </ button >
< button onClick = { scrollToContent } > Scroll to Content </ button >
</ div >
< div style = { { flex: 1 } } >
< Excalidraw excalidrawAPI = { handleExcalidrawAPI } />
</ div >
</ div >
);
}
Event Handlers
Available Callbacks
Excalidraw provides various event handlers:
onChange
Called when elements, app state, or files change: < Excalidraw
onChange = { ( elements , appState , files ) => {
console . log ( "Scene changed" );
} }
/>
onPointerUpdate
Track pointer/cursor movements: < Excalidraw
onPointerUpdate = { ( payload ) => {
console . log ( "Pointer:" , payload . pointer );
} }
/>
onPointerDown / onPointerUp
Handle pointer events: < Excalidraw
onPointerDown = { ( activeTool , pointerDownState ) => {
console . log ( "Pointer down" , activeTool );
} }
onPointerUp = { ( activeTool , pointerDownState ) => {
console . log ( "Pointer up" , activeTool );
} }
/>
onScrollChange
Monitor scroll position: < Excalidraw
onScrollChange = { ( scrollX , scrollY ) => {
console . log ( "Scroll:" , scrollX , scrollY );
} }
/>
onLibraryChange
Track library modifications: < Excalidraw
onLibraryChange = { ( libraryItems ) => {
console . log ( "Library updated:" , libraryItems );
} }
/>
Customizing the UI
Rendering Custom Components
Add custom UI elements to Excalidraw:
import { Excalidraw } from "@excalidraw/excalidraw" ;
import { useState } from "react" ;
function App () {
const [ excalidrawAPI , setExcalidrawAPI ] = useState ( null );
return (
< Excalidraw
excalidrawAPI = { ( api ) => setExcalidrawAPI ( api ) }
renderTopRightUI = { () => (
< div style = { { display: "flex" , gap: "10px" , padding: "10px" } } >
< button
onClick = { () => {
const elements = excalidrawAPI ?. getSceneElements ();
console . log ( ` ${ elements ?. length } elements` );
} }
>
Count Elements
</ button >
< button
onClick = { () => {
excalidrawAPI ?. resetScene ();
} }
>
Clear All
</ button >
</ div >
) }
renderTopLeftUI = { () => (
< div style = { { padding: "10px" } } >
< h3 style = { { margin: 0 } } > My Drawing App </ h3 >
</ div >
) }
/>
);
}
Using Built-in Components
Excalidraw exports reusable components:
import {
Excalidraw ,
MainMenu ,
WelcomeScreen ,
Footer ,
} from "@excalidraw/excalidraw" ;
function App () {
return (
< Excalidraw >
< WelcomeScreen >
< WelcomeScreen.Hints.MenuHint />
< WelcomeScreen.Hints.ToolbarHint />
< WelcomeScreen.Center >
< WelcomeScreen.Center.Heading >
Welcome to My Drawing App!
</ WelcomeScreen.Center.Heading >
< WelcomeScreen.Center.Menu >
< WelcomeScreen.Center.MenuItemLink href = "https://example.com" >
Learn More
</ WelcomeScreen.Center.MenuItemLink >
</ WelcomeScreen.Center.Menu >
</ WelcomeScreen.Center >
</ WelcomeScreen >
< MainMenu >
< MainMenu.DefaultItems.SaveAsImage />
< MainMenu.DefaultItems.Export />
< MainMenu.DefaultItems.Help />
</ MainMenu >
</ Excalidraw >
);
}
From packages/excalidraw/index.tsx:283-294, Excalidraw exports several built-in components like MainMenu, WelcomeScreen, Footer, and Sidebar that can be customized.
Collaboration Support
Enabling Collaboration
Prepare Excalidraw for real-time collaboration:
import { Excalidraw } from "@excalidraw/excalidraw" ;
import { useState } from "react" ;
function App () {
const [ collaborators , setCollaborators ] = useState ( new Map ());
return (
< Excalidraw
isCollaborating = { true }
onPointerUpdate = { ( payload ) => {
// Broadcast pointer position to other users
// and update collaborators map
} }
/>
);
}
Advanced Integration
With Next.js
Embed in a Next.js application:
import dynamic from "next/dynamic" ;
// Import Excalidraw dynamically to avoid SSR issues
const Excalidraw = dynamic (
() => import ( "@excalidraw/excalidraw" ). then (( mod ) => mod . Excalidraw ),
{ ssr: false }
);
export default function DrawingPage () {
return (
< div style = { { height: "100vh" } } >
< Excalidraw />
</ div >
);
}
With State Management
Integrate with Redux or other state management:
import { Excalidraw } from "@excalidraw/excalidraw" ;
import { useDispatch , useSelector } from "react-redux" ;
import { updateDrawing } from "./store/drawingSlice" ;
function App () {
const dispatch = useDispatch ();
const drawing = useSelector (( state ) => state . drawing );
const handleChange = ( elements , appState , files ) => {
dispatch ( updateDrawing ({ elements , appState , files }));
};
return (
< Excalidraw
initialData = { drawing }
onChange = { handleChange }
/>
);
}
Custom File Handling
Control file ID generation:
import { Excalidraw } from "@excalidraw/excalidraw" ;
import { nanoid } from "nanoid" ;
function App () {
return (
< Excalidraw
generateIdForFile = { ( file ) => {
// Custom file ID generation
return `custom- ${ nanoid () } ` ;
} }
/>
);
}
Link Handling
Custom Link Behavior
Control how links are opened:
import { Excalidraw } from "@excalidraw/excalidraw" ;
function App () {
return (
< Excalidraw
onLinkOpen = { ( element , event ) => {
const link = element . link ;
// Custom link handling logic
if ( link ?. startsWith ( "/" )) {
// Internal link - handle with router
event . preventDefault ();
// navigate(link);
}
// External links open normally
} }
/>
);
}
Embeddable Elements
Validating Embeddables
Control which URLs can be embedded:
import { Excalidraw } from "@excalidraw/excalidraw" ;
function App () {
return (
< Excalidraw
validateEmbeddable = { ( link ) => {
// Only allow specific domains
const allowedDomains = [
"youtube.com" ,
"youtu.be" ,
"vimeo.com" ,
"figma.com" ,
];
try {
const url = new URL ( link );
return allowedDomains . some (( domain ) =>
url . hostname . includes ( domain )
);
} catch {
return false ;
}
} }
renderEmbeddable = { ( element , appState ) => {
// Custom rendering for embeddables
return null ; // Use default rendering
} }
/>
);
}
Complete Integration Example
Full-Featured App
import {
Excalidraw ,
MainMenu ,
WelcomeScreen ,
exportToBlob ,
serializeAsJSON ,
restoreElements ,
restoreAppState ,
} from "@excalidraw/excalidraw" ;
import { useState , useEffect , useCallback } from "react" ;
function App () {
const [ excalidrawAPI , setExcalidrawAPI ] = useState ( null );
const [ theme , setTheme ] = useState ( "light" );
const [ initialData , setInitialData ] = useState ( null );
// Load saved data
useEffect (() => {
try {
const saved = localStorage . getItem ( "my-drawing-app" );
if ( saved ) {
const data = JSON . parse ( saved );
setInitialData ({
elements: restoreElements ( data . elements || [], null ),
appState: restoreAppState ( data . appState || {}, null ),
files: data . files || {},
});
}
} catch ( error ) {
console . error ( "Failed to load:" , error );
}
}, []);
// Auto-save
const handleChange = useCallback (( elements , appState , files ) => {
const data = serializeAsJSON ( elements , appState , files , "local" );
localStorage . setItem ( "my-drawing-app" , data );
}, []);
// Export handler
const handleExport = async () => {
if ( ! excalidrawAPI ) return ;
const blob = await exportToBlob ({
elements: excalidrawAPI . getSceneElements (),
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 ();
URL . revokeObjectURL ( url );
};
if ( ! initialData ) return < div > Loading... </ div > ;
return (
< div style = { { height: "100vh" , display: "flex" , flexDirection: "column" } } >
< div
style = { {
padding: "10px" ,
background: theme === "dark" ? "#1e1e1e" : "#f0f0f0" ,
color: theme === "dark" ? "#fff" : "#000" ,
display: "flex" ,
gap: "10px" ,
alignItems: "center" ,
} }
>
< h2 style = { { margin: 0 } } > My Drawing App </ h2 >
< button onClick = { handleExport } > Export PNG </ button >
< button onClick = { () => setTheme ( theme === "light" ? "dark" : "light" ) } >
Toggle Theme
</ button >
< button onClick = { () => excalidrawAPI ?. resetScene () } > Clear </ button >
</ div >
< div style = { { flex: 1 } } >
< Excalidraw
excalidrawAPI = { ( api ) => setExcalidrawAPI ( api ) }
theme = { theme }
initialData = { initialData }
onChange = { handleChange }
UIOptions = { {
canvasActions: {
changeViewBackgroundColor: true ,
clearCanvas: false , // Using custom clear button
},
} }
>
< WelcomeScreen >
< WelcomeScreen.Center >
< WelcomeScreen.Center.Heading >
Welcome to My Drawing App!
</ WelcomeScreen.Center.Heading >
</ WelcomeScreen.Center >
</ WelcomeScreen >
< MainMenu >
< MainMenu.DefaultItems.SaveAsImage />
< MainMenu.DefaultItems.Export />
< MainMenu.Separator />
< MainMenu.DefaultItems.Help />
</ MainMenu >
</ Excalidraw >
</ div >
</ div >
);
}
export default App ;
Best Practices
Set container height
Always ensure Excalidraw’s parent has a defined height.
Handle API carefully
Check if excalidrawAPI is available before calling methods.
Debounce auto-save
Use debouncing for auto-save to avoid performance issues.
Use dynamic imports
For Next.js or SSR, use dynamic imports with ssr: false.
Validate user input
Always validate embeddable URLs and custom data.
Next Steps