Redesigning Excalidraw’s Component API: From Render Props to Child Components
The article explains how Excalidraw’s new component API replaces render props with a child‑component approach, introduces customizable MainMenu, Footer, and WelcomeScreen components, provides code examples, and discusses the benefits and limitations of this redesign for developers building custom whiteboard applications.
This article is a translation of “Rethinking the Component API” for Excalidraw, a popular hand‑drawn style whiteboard app that provides an editor and related components for developers.
Since the redesign announcement last year, many developers asked when the new API would be released to the Excalidraw package and why it wasn’t available from the start.
The delay was due to the need to support custom development. In the public app, some content such as the main menu and welcome screen is hard‑coded, which many private applications don’t need, so a more flexible API was required.
Today the initial version of the new API is released; the previously blocking features are now customizable. If feedback is positive, more APIs will be opened to let developers tailor the editor to their users’ needs.
The redesign introduces two major UI pieces: a dropdown menu in the top‑left corner (MainMenu) and a welcome screen with tips for new users.
Dropping render props
Instead of using render props, the new API lets you render any component as a child of
Excalidraw. Example:
<code>import { Excalidraw, MainMenu, Footer } from '@excalidraw/excalidraw';
import { MyCustomButton } from './MyCustomButton';
export const App = () => (
<Excalidraw>
<MainMenu>{/* menu items */}</MainMenu>
<Footer>
<MyCustomButton />
</Footer>
</Excalidraw>
);
</code>Future versions may expose plugins as components, e.g.:
<code>import { Excalidraw, MinimapPlugin } from "@excalidraw/excalidraw";
export const App = () => (
<Excalidraw>
<MainMenu>{/* menu items */}</MainMenu>
<MinimapPlugin />
</Excalidraw>
);
</code>This change is more of an aesthetic decision; the API surface is smaller because you no longer need to pass render props.
The goal is also to separate UI from the editor core, making the core usable without React.
To identify which children correspond to which UI parts, we use the legacy
React.ChildrenAPI. Simplified utility:
<code>export const getReactChildren = <ExpectedChildren extends {
[k in string]?: React.ReactNode;
}>(children: React.ReactNode) => {
return React.Children.toArray(children).reduce((acc, child) => {
if (React.isValidElement(child)) {
acc[child.type.displayName] = child;
}
return acc;
}, {} as Partial<ExpectedChildren>);
};
</code>In practice we validate expected child names and render them accordingly, allowing any UI component to be a top‑level child of
Excalidrawwithout worrying about order.
Limitation: the component must be a direct child of
Excalidraw. Nesting a
Footerinside another component prevents detection, e.g.:
<code>const MyFooter = () => {
return <Footer />;
};
const App = () => (
<Excalidraw>
{/* nope :( */}
<MyFooter />
</Excalidraw>
);
</code><MainMenu/>
The new editor design adds a dropdown menu in the top‑left corner, which can be fully customized.
Below is the menu on excalidraw.com (left) versus the default menu rendered from the package (right).
If the default options don’t suit you, you can render your own
MainMenucomponent, using either the built‑in menu items or completely custom ones.
<code>import { Excalidraw, MainMenu } from "@excalidraw/excalidraw";
const App = () => (
<Excalidraw>
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.Export />
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.Separator />
<MainMenu.Item onSelect={() => alert("Hello to you too!")}>Hello!</MainMenu.Item>
</MainMenu>
</Excalidraw>
);
</code>As with
Footer, ensure it is a top‑level child of
Excalidraw.
<WelcomeScreen/>
The redesign also introduces a
WelcomeScreencomponent, which is composed of several sub‑elements rendered in different UI locations.
The two main parts are a central area with a logo and quick actions, and hint components that point out UI features.
You can customize most of the content, rendering only the center part, changing hints, etc. Note that
WelcomeScreenmust be a top‑level child, and its subcomponents like
WelcomeScreen.Centerand
WelcomeScreen.Hintsmust be direct children of
WelcomeScreen.
<code>import { Excalidraw, WelcomeScreen } from "@excalidraw/excalidraw";
const App = () => (
<Excalidraw>
<WelcomeScreen>
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>You can draw anything you want!</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemHelp />
<WelcomeScreen.Center.MenuItemLiveCollaborationTrigger onSelect={() => setCollabDialogShown(true)} />
{!isExcalidrawPlusSignedUser && (
<WelcomeScreen.Center.MenuItem onSelect={() => console.log("doing something!")}>Do something</WelcomeScreen.Center.MenuItem>
)}
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
</WelcomeScreen>
</Excalidraw>
);
</code>Summary
You can now read more about the newly introduced APIs in the documentation. We are improving the docs and will soon publish detailed examples on handling specific cases. Stay tuned! 💜
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.