I made a custom Payload CMS block that allows to create and update excalidraw diagrams within the CMS. It supports dark and light mode switching and rendering inline or as external SVG.
And last weekend I added MCP server with Oauth so I could generate and update those diagrams and add them to post drafts from Claude. I think it is more convenient since I don't have to use API billing model and don't need to build a custom UI.
Here is an example post: https://www.janhouse.lv/blog/network/self-hosting-tailscale-...
Originally I wanted to sync posts from Obsidian but it doesn't have good enough image handling which I sometimes need and I needed extra metadata to unlist or password protect or noindex some posts.
[0] -- https://github.com/excalidraw/excalidraw-mcp
[1] -- https://modelcontextprotocol.io/extensions/apps/overview
Now it reads like an ad for some extension to a program I've never heard about.
Also, and that's personal, I think it's cute.
Depends on what you want to achieve with your look. Do you want to scream professionalism, authority, and completed? Use a regular UML tool.
Want to say this is a rough draft of a few ideas? Then using UML is probably THE wrong look. And Exaclidraw should be used instead.
--- Anecdote time. According to one of my professors, they showed how the prototype will look in action, and the customers were so impressed by the smoke and mirrors prototype they wanted to start using it right away.
In the end, customer walked away because they thought they were being strung along to pay for something that was already done.
It has an excellent UI, selections work way better than Lucid or Figma etc, the sketchy look makes it clear designs are rough and not blueprints, it's private and loads instantly.
The one negative is that it's a pain to get the multiplayer self-hosted version running.
TLDraw: https://www.tldraw.com/
Excalidraw: https://excalidraw.com/
And the GitHub repo says: An open source virtual hand-drawn style whiteboard. Collaborative and end-to-end encrypted.
It's the intended design...
TikzMaker: https://tikzmaker.com/
But in general AI-diagramming is still unsolved; needs several iterations to get rid of wonky/wrong arrows, misplaced boxes, misplaced text etc.
I wanted to ask you: is there's a reason you use a separate svg file for each (light/dark) mode?
A single SVG file using CSS can change it's own colors based on the user's preference. I have an example here: http://alejo.ch/3jj - the 3 plots should honor your mode (I put the generator code here: https://github.com/alefore/mini_svg)
Just figure I'd ask. If you have a good reason for using separate files, I'd love to hear it (because it probably would also apply to what I'm doing). :)
I'm a huge fan of anything related to code that can I check into git, track its evolution and the thinking that went behind it. Why was Kubernetes chosen? Why was NATs chosen? Why are the topics named the way they are?
I am a huge fan of mermaid diagrams because it lets me check in my diagrams into git. I am a huge fan of mermaid diagrams because my code can generate diagrams that I (or they) can check into git - and this was before AI.
Now that AI can generate mermaid diagrams, people look at my Git repos and go "oh, you use AI a lot!" - then I point to my git history and they see it's from 2018.
I'm really happy that mermaid and related tools like Excalidraw are taking off - we have another chance at documentation being automated, uptodate and "fresh".
I have tried a lot of tools in this space. If it comes out looking alright, that's usually because it was so simple that it didn't actually need a diagram. Anything with a bit of non trivial structure seems to quickly escalate with essentially no good options other then esoteric hacks with styling to make it look any good.
This seems to be a thing where you can have pretty automated layouts, complex diagrams, or correct diagrams and can only have two out of three.
Which means that almost 100% of my use cases for these tools never really work for me unless I sit down and grab some old school drawing tool (or just give up on the whole notion, which is much more likely). If it was trivial, I wouldn't bother making a diagram. These tools seem only usable for stuff where diagrams were overkill to begin with. I saw no examples on the linked article (and the rest of the site; I browsed the top few recent articles) to really counter this.
A self-hosted version with storage (multiplayer) plus any Claude access would be a killer setup for team planning etc and let us drop Miro.
For a presentation or project documentation artifacts, I can then go thru my sketches and pull bigger pictures together easily. I applied Fowler's "sketch" principle in my own tool ormle.com
It’s a lot of work, as starting over is often easier than reviews and edits. Usually the diagrams are slightly out of date, but good enough to satisfy whoever is looking at it.
I wish I had a better solution. Now I’m wondering I could write something that walks the code for me.
[1]: https://darshanmakwana412.github.io/2026/03/a-system-of-jour...
It can be exceedingly useful to do this if you have a big log file and you need to have a diagram of a trace, even if you're not sharing it on a blog.
Do use diagrams to explain an abstraction, and attach a word to it. Don't use diagrams to represent the exact state of a system.
That way, the diagram source and
The three-lines-menu also has a "Save to..." option that lets you create a sharable link or save to your local disk.
:root { --varname: #FFE; }
@media (prefers-color-scheme: dark) {
:root { --varname: #022; }
}
Edit: To expand on this, I dislike light/dark toggles. If I have dark mode on, seeing a bright screen and having to search for a toggle is jarring. Just show me the colours according to my preference!TL;DR I use an Excalidraw, wrap the elements of interest with a frame, name it with export_ prefix, my forked excalidraw extension automatically generates SVGs for light and dark mode.
I used Excalidraw a lot in the past.
Just recently a new usecase evolved.
While writing my first article the dependency between graphics and the text lead to a lot frustration. Fine-tuning the graphic led to an easier text. Changes in the text made me realize that some information in the graphic is not needed to grasp what should land.
Every change in a graphic in Excalidraw meant 9 clicks in Excalidraw.
It took me about 45 seconds.
Automate it :-) .

Image © Randall Munroe, XKCD.com
...20 minutes later... A bit of bash thanks to open source (specifically JonRC's excalirender) - it worked...
A little GitHub action that:
See code here:
name: Export Excalidraw Frameson: push: branches: - main pull_request: branches: - mainpermissions: contents: writejobs: export-frames: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2 # Fetch last 2 commits to compare with HEAD~1 - name: Get changed Excalidraw files id: changed-files run: | # Get list of changed .excalidraw files since last commit if [[ "${{ github.event_name }}" == "push" ]]; then CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD | grep '\.excalidraw$' || true) else # For PRs, compare with target branch CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep '\.excalidraw$' || true) fi if [ -z "$CHANGED_FILES" ]; then echo "No changed .excalidraw files found" echo "has_changes=false" >> $GITHUB_OUTPUT else echo "Changed files:" echo "$CHANGED_FILES" echo "$CHANGED_FILES" > /tmp/changed_files.txt echo "has_changes=true" >> $GITHUB_OUTPUT fi - name: Install excalirender if: steps.changed-files.outputs.has_changes == 'true' run: | curl -fsSL https://raw.githubusercontent.com/JonRC/excalirender/main/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH excalirender --version || echo "excalirender installed" - name: Export frames for changed files if: steps.changed-files.outputs.has_changes == 'true' run: | # Create export script cat > /tmp/export_frames.sh << 'EOF' #!/bin/bash EXCALIDRAW_FILE="$1" OUTPUT_DIR="$(dirname "$EXCALIDRAW_FILE")" # Read the Excalidraw file and extract frame names FRAME_NAMES=$(jq -r '.elements[] | select(.type == "frame") | .name // "frame-" + .id' "$EXCALIDRAW_FILE") if [ -z "$FRAME_NAMES" ]; then echo "No frames found in $EXCALIDRAW_FILE" exit 0 fi echo "Exporting frames from $EXCALIDRAW_FILE" # Export each frame in light and dark mode while IFS= read -r frame_name; do if [ -n "$frame_name" ]; then echo " Exporting frame: $frame_name" # Sanitize frame name for use in filename safe_name=$(echo "$frame_name" | sed 's/[<>:"/\\|?*]/-/g' | sed 's/\s+/-/g') # Export light mode excalirender "$EXCALIDRAW_FILE" --frame "$frame_name" -o "${OUTPUT_DIR}/${safe_name}-light.svg" # Export dark mode excalirender "$EXCALIDRAW_FILE" --frame "$frame_name" --dark -o "${OUTPUT_DIR}/${safe_name}-dark.svg" fi done <<< "$FRAME_NAMES" echo " ✓ Exported all frames from $EXCALIDRAW_FILE" EOF chmod +x /tmp/export_frames.sh # Process each changed file while IFS= read -r file; do if [ -n "$file" ]; then echo "Processing: $file" /tmp/export_frames.sh "$file" fi done < /tmp/changed_files.txt - name: Commit exported SVGs if: steps.changed-files.outputs.has_changes == 'true' run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" # Add all newly created SVG files git add **/*.svg 2>/dev/null || git add *.svg 2>/dev/null || true # Check if there are any changes to commit if git diff --staged --quiet; then echo "No new SVG files to commit" else echo "Committing exported SVG files" git commit -m "chore: export Excalidraw frames as SVGs - Exported frames from changed .excalidraw files - Generated light and dark mode variants 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>" git push fi
Awesome! Enough to continued working on my article.
After working with this approach for some time I faced various issues.
I circumvented 1.) with additional labels added but 2.) broke the whole concept. Not being able to run the export locally meant I needed to push the Excalidraw file to GitHub, wait for the pipeline to finish, and pull the new commit before I could see new images or changes in images reflected.
So the solution kind of worked but reviewing the blog post locally was only possible with outdated images.
What if Excalidraw's VSCode extension would check the open *.excalidraw file for changes and automatically export each frame as two separate SVG files - one in dark mode, one in light mode?
I took some time with Claude over the weekend to YOLO code. The result:
If I edit my Excalidraw in VSCode, all I need to do to make a section available for my blog post:
export_${image_name}The extension will pick up the frame, export it as SVG in dark and light mode, and save two SVGs named ${image_name}.light.exp.svg and ${image_name}.dark.exp.svg next to the Excalidraw file.
Now that those images are available locally and update whenever I change a frame in my Excalidraw, I can reference them via auto-complete and preview in the editor, see them rendered in the Preview tab.
I am pretty happy with the result. I spent only a couple of hours including this writeup. Using the tool brings joy since it solves a real pain.
I can't wait to use it extensively in the articles in the making - SQLite on Git.
One thing I'm not sure about, though. After talking to others about this approach I could see my approach bringing value to the original Excalidraw extension itself. But I wouldn't create a pull request - since I don't own the code - or rather, I don't want to take ownership. I'm thinking to open an issue, describe the problem and the solution to serve as inspiration instead.
If others find this useful and play around with it - I created artifacts for the release section in my GitHub fork that allows others to download and use my extension. For now, that's enough!