* feat(editor): wrap typed & pasted text at 800, clear of the side panels A free text growing as it's typed stopped only at the view's width (#12175), so on a large screen it ran to 2000+ px before wrapping, and a pasted text wrapped at half the view. Both now follow one rule, in `getMaxTextWidth`: - no wider than TEXT_MAX_WRAP_WIDTH (800, in @excalidraw/common) in scene units, at any zoom: the paste's former cap, now shared. A paste no longer wraps at half the view. - nor wider than the canvas between the side panels, less TEXT_VIEWPORT_PADDING at each side: the docked sidebar, as before; the full styles panel (desktop), which shows next to a text while it's edited, and once it's pasted and selected (pasting with nothing selected, its room is reserved: as last measured, else approximately); and the stats panel, only for a text beside it, i.e. with rows within that padding of it, as it covers only the top of the canvas' side. A text that starts wrapping is brought in between the same panels, as before. A paste is laid out as if the stats panel weren't there, and again, clear of it, only if it lands beside it. As the stats panel grows to show a selection's properties, with it open the paste is rendered selected first (not captured yet, via `flushSync`), so the panels are measured as they'll be; laid out again, it stays the same elements, and one undo step. A wrapped paste is then moved into view (as far as the view would have scrolled), instead of scrolling the view. The stats panel is marked as side viewport UI (`data-viewport-ui="side"`, named "stats"), so the viewport offsets' other users (zoom to fit, scroll to content, search, a host's `setViewport` with UI offsets) keep clear of it too while it's open. `AppViewport` gains `getSideInsets(names, { reserve })`, generalizing `getSidebarInsets`, and `getSideUIRect(name)`; `getOffsets` shares the reserve fallback through `getReservedSide`. Tests: the cap at 50% and 200%; a typed text clear of the styles and the stats panel, and of the stats panel only beside it; a paste wrapping as a typed one, with the styles panel's room reserved; a paste clear of the stats panel only where it lands beside it, and of the stats panel grown to show it, as one undo step; a wrapped paste moved into view, the view not scrolled. Claude Opus 5.5 (1M), xhigh effort * feat(editor): pan the canvas when the caret goes under the stats or styles panel The canvas follows the caret past the view's edge, and behind the docked sidebar, through the editor's box, which the browser scrolls to reveal a caret (#12175). The stats and the full styles panel don't span the box's height, so a caret typed in under them stayed out of sight. After each input, once the update (and any scroll it made) is in the app's state, the editor works out where the caret is: its line from `getWrappedTextLines`, its x from the width of the line before it, rotated with the text. In a line with RTL characters (`hasRTLChars`, new in @excalidraw/common) the x comes from the browser's bidirectional layout of the line instead, in its paragraph's direction (the editor's `dir="auto"` makes it `unicode-bidi: plaintext`: each paragraph goes its first strong character's way): with Hebrew then English, the caret after the English is mid-line, not at the line's edge. That's measured through a range in an off-screen mirror of the line, shared with the click-to-caret lookup. If the caret's under one of those panels, the canvas pans to bring it out on the canvas' side, with CARET_FOLLOW_PADDING to spare. Only then: a text beside or below a panel stays where it is. Tests: typing past the stats panel's left edge pans the canvas under it, but not below it; the caret in a bidirectional line, a paragraph after one going the other way, is found where the browser lays it out. Claude Opus 5.5 (1M), xhigh effort
Excalidraw Editor | Blog | Documentation | Excalidraw+
An open source virtual hand-drawn style whiteboard.
Collaborative and end-to-end encrypted.
Features
The Excalidraw editor (npm package) supports:
- 💯 Free & open-source.
- 🎨 Infinite, canvas-based whiteboard.
- ✍️ Hand-drawn like style.
- 🌓 Dark mode.
- 🏗️ Customizable.
- 📷 Image support.
- 😀 Shape libraries support.
- 🌐 Localization (i18n) support.
- 🖼️ Export to PNG, SVG & clipboard.
- 💾 Open format - export drawings as an
.excalidrawjson file. - ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
- ➡️ Arrow-binding & labeled arrows.
- 🔙 Undo / Redo.
- 🔍 Zoom and panning support.
Excalidraw.com
The app hosted at excalidraw.com is a minimal showcase of what you can build with Excalidraw. Its source code is part of this repository as well, and the app features:
- 📡 PWA support (works offline).
- 🤼 Real-time collaboration.
- 🔒 End-to-end encryption.
- 💾 Local-first support (autosaves to the browser).
- 🔗 Shareable links (export to a readonly link you can share with others).
We'll be adding these features as drop-in plugins for the npm package in the future.
Quick start
Note: following instructions are for installing the Excalidraw npm package when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our Development Guide.
Use npm or yarn to install the package.
npm install react react-dom @excalidraw/excalidraw
# or
yarn add react react-dom @excalidraw/excalidraw
Check out our documentation for more details!
Contributing
- Missing something or found a bug? Report here.
- Want to contribute? Check out our contribution guide or let us know on Discord.
- Want to help with translations? See the translation guide.
Integrations
Who's integrating Excalidraw
Google Cloud • Meta • CodeSandbox • Obsidian Excalidraw • Replit • Slite • Notion • HackerRank • and many others
Sponsors & support
If you like the project, you can become a sponsor at Open Collective or use Excalidraw+.
Thank you for supporting Excalidraw
Last but not least, we're thankful to these companies for offering their services for free: