UI Components API
Excalidraw provides a set of composable UI components that can be used to customize the editor interface. These components can be passed as children to the main Excalidraw component.
Import
import {
MainMenu,
Sidebar,
Footer,
WelcomeScreen,
Button,
LiveCollaborationTrigger,
Stats,
DefaultSidebar,
Ellipsify,
TTDDialog,
TTDDialogTrigger,
TTDStreamFetch,
DiagramToCodePlugin,
CommandPalette,
useEditorInterface,
useStylesPanelMode,
} from "@excalidraw/excalidraw";
MainMenu
The MainMenu component provides a customizable dropdown menu accessed via a hamburger icon.
Props
Menu items and components to display in the dropdown. Can include MainMenu.Item, MainMenu.ItemLink, MainMenu.DefaultItems.*, MainMenu.Separator, MainMenu.Group, and MainMenu.Sub.
Callback fired when any menu item is selected (clicked on).onSelect?: (event: Event) => void;
Subcomponents
MainMenu.Item
A clickable menu item that triggers an action.
<MainMenu.Item
onSelect={handleAction}
icon={<MyIcon />}
shortcut="⌘K"
>
Menu Item Label
</MainMenu.Item>
Callback when item is clicked.
Icon to display before the label.
Keyboard shortcut to display (e.g., “⌘K”, “Ctrl+S”).
Label content for the menu item.
MainMenu.ItemLink
A menu item that opens a link in a new tab.
<MainMenu.ItemLink
href="https://example.com"
icon={<LinkIcon />}
shortcut="⌘L"
>
External Link
</MainMenu.ItemLink>
URL to open when clicked.
Icon to display before the label.
Keyboard shortcut to display.
Label content for the menu item.
MainMenu.ItemCustom
A fully custom menu item with complete control over rendering.
<MainMenu.ItemCustom>
<div className="custom-menu-item">
{/* Your custom content */}
</div>
</MainMenu.ItemCustom>
Custom content to render.
MainMenu.Separator
A visual separator between menu items.
No props.
MainMenu.Group
Groups related menu items together.
<MainMenu.Group>
<MainMenu.Item onSelect={handler1}>Item 1</MainMenu.Item>
<MainMenu.Item onSelect={handler2}>Item 2</MainMenu.Item>
</MainMenu.Group>
Menu items to group together.
MainMenu.Sub
Creates a submenu.
<MainMenu.Sub label="More Options" icon={<MoreIcon />}>
<MainMenu.Item onSelect={handler1}>Sub Item 1</MainMenu.Item>
<MainMenu.Item onSelect={handler2}>Sub Item 2</MainMenu.Item>
</MainMenu.Sub>
Label for the submenu trigger.
Icon to display before the label.
Menu items to display in the submenu.
MainMenu.Trigger
Custom trigger component to open the menu (overrides default hamburger icon).
<MainMenu.Trigger>
<button>Open Menu</button>
</MainMenu.Trigger>
Default Items
Excalidraw provides pre-built menu items accessible via MainMenu.DefaultItems:
MainMenu.DefaultItems.LoadScene
Opens a file picker to load a scene from a .excalidraw file.<MainMenu.DefaultItems.LoadScene />
MainMenu.DefaultItems.SaveToActiveFile
Saves the current scene to the active file (requires File System Access API).<MainMenu.DefaultItems.SaveToActiveFile />
MainMenu.DefaultItems.Export
Opens the export dialog to save as PNG, SVG, or JSON.<MainMenu.DefaultItems.Export />
MainMenu.DefaultItems.SaveAsImage
Quickly export the canvas as an image.<MainMenu.DefaultItems.SaveAsImage />
MainMenu.DefaultItems.Help
Opens the help dialog showing keyboard shortcuts.<MainMenu.DefaultItems.Help />
MainMenu.DefaultItems.ClearCanvas
Clears all elements from the canvas.<MainMenu.DefaultItems.ClearCanvas />
MainMenu.DefaultItems.ToggleTheme
Toggles between light and dark themes.<MainMenu.DefaultItems.ToggleTheme />
MainMenu.DefaultItems.ChangeCanvasBackground
Opens a color picker to change the canvas background.<MainMenu.DefaultItems.ChangeCanvasBackground />
Example
import { Excalidraw, MainMenu } from "@excalidraw/excalidraw";
function App() {
const handleCustomAction = () => {
console.log("Custom action triggered");
};
return (
<div style={{ height: "100vh" }}>
<Excalidraw>
<MainMenu onSelect={(e) => console.log("Menu item selected", e)}>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.Export />
<MainMenu.Separator />
<MainMenu.Item onSelect={handleCustomAction} icon={customIcon}>
Custom Action
</MainMenu.Item>
<MainMenu.DefaultItems.Help />
</MainMenu>
</Excalidraw>
</div>
);
}
The Sidebar component provides a customizable sidebar that can be docked or floating.
Props
Unique name for the sidebar. Used to identify and control the sidebar state.name: SidebarName; // string
Sidebar content including Sidebar.Header, Sidebar.Tabs, Sidebar.Tab, etc.
Called on sidebar open/close or tab change.onStateChange?: (state: { name: string; tab?: string } | null) => void;
Parameters:
state - Current open sidebar state, or null if closed
Supply alongside docked prop to make the sidebar user-dockable.onDock?: (docked: boolean) => void;
Controls whether the sidebar is docked. Must be used with onDock to enable user docking.
Additional CSS class name for styling.
Subcomponents
Header component for the sidebar with title and optional dock/close buttons.
<Sidebar.Header>
<h2>My Sidebar</h2>
</Sidebar.Header>
Header content (typically a title).
Button to open/close the sidebar.
<Sidebar.Trigger
name="my-sidebar"
icon={<MyIcon />}
title="Open Sidebar"
/>
Name of the sidebar to trigger (must match the Sidebar’s name prop).
Optional tab name to open when triggering the sidebar.
Icon to display in the trigger button.
Tooltip text for the trigger button.
Additional CSS class name.
Callback when the trigger is clicked.onToggle?: (open: boolean) => void;
Inline styles for the trigger button.
Container for sidebar tabs.
<Sidebar.Tabs>
<Sidebar.Tab tab="tab1">Tab 1 Content</Sidebar.Tab>
<Sidebar.Tab tab="tab2">Tab 2 Content</Sidebar.Tab>
</Sidebar.Tabs>
Individual tab content.
<Sidebar.Tab tab="my-tab">
<div>Tab content goes here</div>
</Sidebar.Tab>
Unique identifier for the tab.
Container for tab trigger buttons.
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="tab1">Tab 1</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="tab2">Tab 2</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
Sidebar.TabTrigger components.
Button to switch between tabs.
<Sidebar.TabTrigger tab="my-tab">
Tab Label
</Sidebar.TabTrigger>
Tab identifier to activate.
Example
import { useState } from "react";
import { Excalidraw, Sidebar } from "@excalidraw/excalidraw";
function App() {
const [docked, setDocked] = useState(true);
return (
<div style={{ height: "100vh" }}>
<Excalidraw>
<Sidebar
name="custom-sidebar"
docked={docked}
onDock={setDocked}
onStateChange={(state) => console.log("Sidebar state:", state)}
>
<Sidebar.Header>
Custom Sidebar
</Sidebar.Header>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="tab1">Tab 1</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="tab2">Tab 2</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
<Sidebar.Tabs>
<Sidebar.Tab tab="tab1">
<div style={{ padding: "1rem" }}>
<h3>Tab 1 Content</h3>
<p>Your custom content here</p>
</div>
</Sidebar.Tab>
<Sidebar.Tab tab="tab2">
<div style={{ padding: "1rem" }}>
<h3>Tab 2 Content</h3>
<p>More custom content</p>
</div>
</Sidebar.Tab>
</Sidebar.Tabs>
</Sidebar>
{/* Sidebar trigger button in the UI */}
<Sidebar.Trigger
name="custom-sidebar"
icon={sidebarIcon}
title="Open Custom Sidebar"
/>
</Excalidraw>
</div>
);
}
The Footer component renders the bottom toolbar with zoom controls and undo/redo buttons. It’s exported for use when building custom layouts, but is typically rendered automatically by Excalidraw.
import { Footer } from "@excalidraw/excalidraw";
The Footer component is usually rendered automatically. You only need to import it if you’re building a completely custom layout.
WelcomeScreen
The WelcomeScreen component displays a welcome message when the canvas is empty.
Props
Custom welcome screen content. If not provided, renders default hints and center content.
Subcomponents
WelcomeScreen.Center
Central welcome message area.
<WelcomeScreen>
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
Welcome to My App!
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
</WelcomeScreen>
Sub-subcomponents:
WelcomeScreen.Center.Logo - Excalidraw logo
WelcomeScreen.Center.Heading - Custom heading text
WelcomeScreen.Center.Menu - Container for menu items
WelcomeScreen.Center.MenuItemLoadScene - Load scene button
WelcomeScreen.Center.MenuItemHelp - Help button
WelcomeScreen.Center.MenuItemLiveCollaborationTrigger - Collaboration button
WelcomeScreen.Hints
UI hints around the editor.
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint />
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
</WelcomeScreen>
Available hints:
WelcomeScreen.Hints.MenuHint - Points to main menu
WelcomeScreen.Hints.ToolbarHint - Points to toolbar
WelcomeScreen.Hints.HelpHint - Points to help button
Example
import { Excalidraw, WelcomeScreen } from "@excalidraw/excalidraw";
function App() {
return (
<div style={{ height: "100vh" }}>
<Excalidraw>
<WelcomeScreen>
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
Start Drawing Amazing Diagrams!
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
<WelcomeScreen.Hints.MenuHint />
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
</WelcomeScreen>
</Excalidraw>
</div>
);
}
A generic button component that follows Excalidraw’s design system.
Props
Callback when button is clicked.
Button content (text, icons, etc.).
type
'button' | 'submit' | 'reset'
default:"button"
HTML button type.
Whether button is in active/selected state.
Additional CSS class name for styling.
All standard HTML button attributes are supported (disabled, style, aria-*, etc.).
Example
import { Button } from "@excalidraw/excalidraw";
function MyComponent() {
const handleClick = () => {
console.log("Button clicked");
};
return (
<Button
onSelect={handleClick}
selected={false}
className="my-custom-button"
disabled={false}
>
Click Me
</Button>
);
}
LiveCollaborationTrigger
A button component for triggering live collaboration mode, displaying the share icon and active collaborator count.
Props
Whether collaboration mode is active.isCollaborating: boolean;
Callback when the button is clicked.
Editor interface details for responsive behavior.editorInterface?: EditorInterface;
All standard HTML button attributes are supported.
Example
import { useState } from "react";
import { Excalidraw, LiveCollaborationTrigger } from "@excalidraw/excalidraw";
function App() {
const [isCollaborating, setIsCollaborating] = useState(false);
const handleCollaboration = () => {
setIsCollaborating(!isCollaborating);
// Your collaboration logic here
};
return (
<div style={{ height: "100vh" }}>
<Excalidraw isCollaborating={isCollaborating}>
<LiveCollaborationTrigger
isCollaborating={isCollaborating}
onSelect={handleCollaboration}
/>
</Excalidraw>
</div>
);
}
Complete Example
Here’s a comprehensive example using multiple UI components together:
import { useState } from "react";
import {
Excalidraw,
MainMenu,
Sidebar,
WelcomeScreen,
LiveCollaborationTrigger,
Button,
} from "@excalidraw/excalidraw";
function App() {
const [isCollaborating, setIsCollaborating] = useState(false);
const [sidebarDocked, setSidebarDocked] = useState(true);
const handleExport = () => {
console.log("Export triggered");
};
return (
<div style={{ height: "100vh" }}>
<Excalidraw isCollaborating={isCollaborating}>
{/* Main Menu */}
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.Export />
<MainMenu.Separator />
<MainMenu.Item onSelect={handleExport} icon={exportIcon}>
Export to Cloud
</MainMenu.Item>
<MainMenu.Separator />
<MainMenu.DefaultItems.Help />
</MainMenu>
{/* Custom Sidebar */}
<Sidebar
name="my-sidebar"
docked={sidebarDocked}
onDock={setSidebarDocked}
>
<Sidebar.Header>Tools</Sidebar.Header>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="shapes">Shapes</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="assets">Assets</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
<Sidebar.Tabs>
<Sidebar.Tab tab="shapes">
<div style={{ padding: "1rem" }}>
<h3>Custom Shapes</h3>
{/* Your custom shapes */}
</div>
</Sidebar.Tab>
<Sidebar.Tab tab="assets">
<div style={{ padding: "1rem" }}>
<h3>Assets Library</h3>
{/* Your assets */}
</div>
</Sidebar.Tab>
</Sidebar.Tabs>
</Sidebar>
{/* Welcome Screen */}
<WelcomeScreen>
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
Create Amazing Diagrams
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
<WelcomeScreen.Hints.MenuHint />
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
</WelcomeScreen>
{/* Collaboration Trigger */}
<LiveCollaborationTrigger
isCollaborating={isCollaborating}
onSelect={() => setIsCollaborating(!isCollaborating)}
/>
</Excalidraw>
</div>
);
}
export default App;
Styling
All UI components can be styled using CSS classes:
/* MainMenu */
.main-menu-trigger { }
.main-menu { }
.main-menu-item { }
/* Sidebar */
.excalidraw-sidebar { }
.excalidraw-sidebar.sidebar--docked { }
/* Button */
.excalidraw-button { }
.excalidraw-button.selected { }
/* LiveCollaborationTrigger */
.collab-button { }
.collab-button.active { }
.CollabButton-collaborators { }
Stats
The Stats component displays canvas statistics and element properties in a floating panel. It provides detailed information about selected elements and the overall scene.
Props
app
AppClassProperties
required
The Excalidraw app instance.
Callback when the stats panel is closed.
Render custom statistics in the panel.renderCustomStats?: (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState
) => React.ReactNode;
Subcomponents
Stats.StatsRow
Renders a row in the stats panel.
<Stats.StatsRow columns={2}>
<div>Width</div>
<div>100</div>
</Stats.StatsRow>
Number of columns in the row grid.
Whether this row is a heading.
Stats.StatsRows
Container for multiple stat rows with optional ordering.
<Stats.StatsRows order={1}>
{/* Multiple StatsRow components */}
</Stats.StatsRows>
Display order of this stats group.
Example
import { Stats } from "@excalidraw/excalidraw";
function CustomStats() {
const renderCustomStats = (elements, appState) => (
<Stats.StatsRows order={999}>
<Stats.StatsRow heading>Custom Stats</Stats.StatsRow>
<Stats.StatsRow columns={2}>
<div>Total Elements</div>
<div>{elements.length}</div>
</Stats.StatsRow>
</Stats.StatsRows>
);
return (
<Excalidraw
renderCustomStats={renderCustomStats}
/>
);
}
The DefaultSidebar component provides the default sidebar with library and search functionality.
Props
Custom sidebar tabs to add to the default sidebar.
Whether the sidebar is docked.
Callback when sidebar dock state changes. Pass false to disable docking.onDock?: ((docked: boolean) => void) | false;
Additional CSS class name.
Subcomponents
Trigger button for the default sidebar.
<DefaultSidebar.Trigger
icon={<MyIcon />}
title="Open Sidebar"
/>
Container for adding custom tab triggers to the default sidebar.
<DefaultSidebar.TabTriggers>
<Sidebar.TabTrigger tab="my-custom-tab">
My Tab
</Sidebar.TabTrigger>
</DefaultSidebar.TabTriggers>
Example
import { Excalidraw, DefaultSidebar, Sidebar } from "@excalidraw/excalidraw";
function App() {
return (
<div style={{ height: "100vh" }}>
<Excalidraw>
<DefaultSidebar>
<Sidebar.Tab tab="custom-tab">
<div style={{ padding: "1rem" }}>
Custom tab content
</div>
</Sidebar.Tab>
</DefaultSidebar>
<DefaultSidebar.Trigger />
</Excalidraw>
</div>
);
}
Ellipsify
A utility component that adds text ellipsis overflow handling.
Props
...rest
React.HTMLAttributes<HTMLSpanElement>
All standard HTML span attributes are supported.
Example
import { Ellipsify } from "@excalidraw/excalidraw";
<Ellipsify style={{ maxWidth: "200px" }}>
This is a very long text that will be truncated with ellipsis
</Ellipsify>
TTDDialog
Text-to-Diagram dialog component for AI-powered diagram generation.
Props
Callback when text is submitted for diagram generation.onTextSubmit: (props: {
prompt: string;
sessionId: string;
}) => Promise<{
generatedResponse: string;
error: Error | null;
}>;
Render custom welcome screen content.renderWelcomeScreen?: () => React.ReactNode;
Render custom warning message.renderWarning?: () => React.ReactNode;
persistenceAdapter
TTDPersistenceAdapter
required
Adapter for persisting chat history.persistenceAdapter: {
load: () => Promise<SavedChats | null>;
save: (chats: SavedChats) => Promise<void>;
clear: () => Promise<void>;
};
Subcomponents
TTDDialog.WelcomeMessage
Default welcome message component.
<TTDDialog.WelcomeMessage />
Example
import { Excalidraw, TTDDialog } from "@excalidraw/excalidraw";
function App() {
const handleTextSubmit = async ({ prompt }) => {
// Call your AI API
const response = await fetch("/api/generate-diagram", {
method: "POST",
body: JSON.stringify({ prompt }),
});
const data = await response.json();
return {
generatedResponse: data.mermaidCode,
error: null,
};
};
const persistenceAdapter = {
load: async () => {
const saved = localStorage.getItem("ttd-chats");
return saved ? JSON.parse(saved) : null;
},
save: async (chats) => {
localStorage.setItem("ttd-chats", JSON.stringify(chats));
},
clear: async () => {
localStorage.removeItem("ttd-chats");
},
};
return (
<Excalidraw>
<TTDDialog
onTextSubmit={handleTextSubmit}
persistenceAdapter={persistenceAdapter}
/>
</Excalidraw>
);
}
TTDDialogTrigger
Trigger button to open the Text-to-Diagram dialog.
Props
Custom button label. Defaults to “Text to Diagram”.
Custom icon for the trigger button.
Example
import { MainMenu, TTDDialogTrigger } from "@excalidraw/excalidraw";
<MainMenu>
<TTDDialogTrigger>
Generate Diagram with AI
</TTDDialogTrigger>
</MainMenu>
TTDStreamFetch
Utility function for streaming text-to-diagram API responses.
function TTDStreamFetch(options: {
url: string;
messages: readonly LLMMessage[];
onChunk?: (chunk: string) => void;
extractRateLimits?: boolean;
signal?: AbortSignal;
onStreamCreated?: () => void;
}): Promise<{
generatedResponse?: string;
error: Error | null;
rateLimit?: number;
rateLimitRemaining?: number;
}>;
Parameters
API endpoint URL for streaming.
messages
readonly LLMMessage[]
required
Array of messages to send to the LLM.
Callback for each chunk of streamed text.onChunk?: (chunk: string) => void;
Whether to extract rate limit headers from the response.
AbortSignal for cancelling the request.
Callback when the stream is created.
Example
import { TTDStreamFetch } from "@excalidraw/excalidraw";
const controller = new AbortController();
const result = await TTDStreamFetch({
url: "/api/generate-diagram",
messages: [
{ role: "user", content: "Create a flowchart for user authentication" }
],
onChunk: (chunk) => {
console.log("Received chunk:", chunk);
},
signal: controller.signal,
});
if (!result.error) {
console.log("Generated:", result.generatedResponse);
}
DiagramToCodePlugin
Plugin component for adding diagram-to-code conversion functionality.
Props
Function to generate code from the diagram.generate: (opts: {
elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles;
appState: AppState;
frameId?: string;
}) => Promise<{
code: string;
language: string;
}>;
Example
import { Excalidraw, DiagramToCodePlugin } from "@excalidraw/excalidraw";
function App() {
const generateCode = async ({ elements, frameId }) => {
// Your code generation logic
const code = await convertElementsToCode(elements);
return {
code,
language: "typescript",
};
};
return (
<Excalidraw>
<DiagramToCodePlugin generate={generateCode} />
</Excalidraw>
);
}
CommandPalette
Command palette component for quick access to editor actions via keyboard shortcuts.
Props
customCommandPaletteItems
Custom commands to add to the palette.interface CommandPaletteItem {
label: string;
category: string;
icon?: JSX.Element;
shortcut?: string;
keywords?: string[];
viewMode?: boolean;
predicate?: boolean | PredicateFunction;
perform: (opts: {
actionManager: ActionManager;
event: React.MouseEvent | React.KeyboardEvent | KeyboardEvent;
}) => void;
}
Default Items
The CommandPalette.defaultItems export provides access to pre-built command items:
import { CommandPalette } from "@excalidraw/excalidraw";
// Available default items include:
// - exportImage
// - loadScene
// - clearCanvas
// - changeViewBackgroundColor
// And more...
Example
import { Excalidraw, CommandPalette } from "@excalidraw/excalidraw";
function App() {
const customCommands = [
{
label: "Export to Cloud",
category: "Export",
icon: <CloudIcon />,
keywords: ["save", "upload"],
perform: ({ actionManager }) => {
// Your export logic
console.log("Exporting to cloud...");
},
},
];
return (
<Excalidraw>
<CommandPalette customCommandPaletteItems={customCommands} />
</Excalidraw>
);
}
The command palette opens with Cmd/Ctrl + Shift + P or Cmd/Ctrl + /.
Hooks
useEditorInterface
Hook to access the editor’s interface information including form factor and sidebar visibility.
function useEditorInterface(): {
formFactor: "desktop" | "tablet" | "phone";
isSidebarDocked: boolean;
};
Example:
import { useEditorInterface } from "@excalidraw/excalidraw";
function MyComponent() {
const editorInterface = useEditorInterface();
if (editorInterface.formFactor === "phone") {
return <MobileView />;
}
return <DesktopView />;
}
useStylesPanelMode
Hook to get the current styles panel mode derived from the editor interface.
function useStylesPanelMode(): "inline" | "popover";
Example:
import { useStylesPanelMode } from "@excalidraw/excalidraw";
function MyComponent() {
const mode = useStylesPanelMode();
return (
<div className={`styles-panel-${mode}`}>
{/* Your component content */}
</div>
);
}
See Also