The open source canvas for your agent.
Brands, apps, launch pages, diagrams, dashboards. Your agent draws them in plain HTML and CSS on your machine, and you steer with comments.
ponzu.design · Connect an agent · The .design format · Embed it
Ponzu is an infinite canvas of artboards. Agents draw on it over MCP, writing HTML and CSS, and you watch it happen live. You leave comments on the canvas; the agent picks them up, makes the change, and resolves them. There are no manual editing panels. If you want something changed, you ask.
- Free forever. MIT. No account, no seats, no limits. Bring the agent you already use.
- Your files, your workflow. A
.designis a folder of HTML files on your disk. Commit it next to your code, sync it through Dropbox, or toss it. - Just HTML and CSS. If a browser can do it, so can your design: grid, gradients, variable fonts, video, real animation. Agents watch their animations and hover states frame by frame, not just the first frame.
bunx ponzu serveThis opens the canvas in your browser and serves MCP at http://127.0.0.1:7777/mcp. Every .design folder under the current directory is opened. It needs Bun; bun add -g ponzu keeps a ponzu command around.
To change a design someone committed to GitHub, open it by its path:
bunx ponzu open north-brook/ponzu/examples/kettle.designThis clones the repo into ~/Ponzu/north-brook/ponzu (or reuses the clone there) and serves that design. It takes the same path as the web viewer, with @branch after the repo, or a ponzu.design/gh or github.com link.
Or download the Mac app (Apple silicon, signed and notarized; it updates itself). Windows (x64) and Linux (x64, an AppImage or a .deb) builds are in beta, on the releases page; they update themselves too, and the Windows one isn't signed yet, so SmartScreen asks before its first launch. It opens .design folders like documents, and on first launch it sets up Claude Code, Codex and Cursor for you. There's nothing else to install or keep running.
Its home screen's Suggested tab proposes designs for the projects your agents worked in lately. When you open it (at most every ten minutes, and only to top it up to eight), Ponzu runs your own Claude Code (or Codex, without Claude Code) headless and read-only, on your agent's account, in two steps: a glance at each project's file list and readme, with no tools, names a few designs, then one run for each reads the code it starts from and writes its prompt. Claude Code gets only Read, Glob and Grep, Codex its read-only sandbox, and neither gets any MCP servers. You copy the prompt you like into your agent. The ideas are kept in ~/.ponzu/suggestions.json. Your other Macs' Ponzu show theirs too, for their own projects, over your tailnet, and opening the tab asks each to look again.
The app also puts ponzu in your terminal, running from inside the app so it updates with it. It links the command into a folder your shell already searches that you can write to (~/.local/bin, ~/bin or /usr/local/bin), never over another ponzu. If there's no such folder, choose Ponzu › Install Terminal Command…, which asks for your password to add it to /usr/local/bin.
The Mac app does this on launch. Without it:
ponzu install # every agent found: Claude Code, Codex, Cursor
ponzu install claude # or just oneAgents get a command to launch, ponzu mcp, rather than a URL. It finds the running Ponzu (the app or ponzu serve) on whatever port it's on, so the agent shares your canvas. If the app is installed but not running, it starts it in the background, with no window until you open Ponzu. With neither, it runs the tools itself on the .design folders under the agent's project. With the app installed, the command runs from inside the app, so it updates with it.
Any other MCP client can launch ponzu mcp over stdio, or use http://127.0.0.1:7777/mcp while Ponzu runs. ponzu install claude --url <url> registers a URL, e.g. a Ponzu on another machine.
Claude Code and Codex can also install Ponzu as a plugin. It adds Ponzu's skills (start a design from a brief, bring a screen into Ponzu, explore variations, make a product shot, implement a design, watch your comments) and the same server the app sets up. It launches the Mac app's server, so open the app once first.
claude plugin marketplace add north-brook/ponzu && claude plugin install ponzu@ponzu
codex plugin marketplace add north-brook/ponzu && codex plugin add ponzu@ponzuWith the app's own setup there too, each agent keeps one Ponzu server, not two. The plugin lives in plugin/.
Then ask for something:
Design a landing page for a houseplant shop in a new
design/shop.design. Give me three takes on the hero.
When an agent points you at a design, it gives you a link rather than a folder path, so it opens in Ponzu instead of your editor: https://ponzu.design/local#mac=workstation&file=/path/to/app.design&artboard=ab_hero for the Mac app, or the canvas's own http://localhost:7777/?file=… under ponzu serve. page and artboard are optional: page=<id> opens on that page, artboard=<id> on that board, and with both it opens on the board when it's on that page, otherwise on the page. mac is the Mac the design is on, by its Tailscale name, so the link opens on your other Macs too, through Ponzu's link between them; without Tailscale it's left out and the link opens the path on the Mac you click it on. The ponzu.design page hands the design to the app as ponzu://open?mac=…&file=… (chats drop links in schemes they don't know) or offers the download if Ponzu isn't installed; the path stays after the #, which browsers never send to the site. /local sits beside /gh for designs on GitHub.
To go back and forth on a design, use the watch prompt (or say "watch my comments in Ponzu"). The agent watches the canvas for your comments, handles each one and resolves it, replying only to answer a question or flag something. Agents also start watching on their own once they've put work up for you to review, and you can still chat with them while they do.
ponzu serve --host 0.0.0.0 # or your tailnet IP
ponzu install claude --url http://<that machine>:7777/mcpPonzu has no auth. Bind beyond localhost only on networks you trust, such as a tailnet.
A .design is a folder. macOS shows it as a single document; everything else sees a directory.
weft.design/
design.json { "ponzu": 1, "name": "Weft", "pages": [{ "id", "name", "icon" }] }
tokens.css :root { --color-signal: #FF5A1F; … }
comments.json { "threads": [{ "id", "artboardId", "nodeId", "x", "y", "status", "messages", … }] }
notes/brief.md notes the agent keeps between sessions
assets/ images and fonts
pages/
website/
landing.html one artboard per file
Each artboard file holds one <section data-artboard>. Its size is the width and height in its style (1440×900 without them); data-x and data-y only place it on the page.
<section data-artboard data-id="ab_dr6yq" data-name="Landing" data-x="0" data-y="0" style="width:1440px;height:7216px;background-color:var(--color-paper)">
<h1 data-id="n_g9oar7w" style="font:700 80px/0.95 var(--font-display);letter-spacing:-0.05em">Write locally. Sync everywhere.</h1>
</section>- Edit it however you like. Ponzu watches the folder. An edit from your editor,
sedor another agent shows up live and becomes an event agents can react to. - Ids are lazy. Hand-written elements get stable ids derived from their position. Ponzu writes an id into a file only when something needs to point at it.
- Inert by default.
<script>,on*handlers andjavascript:URLs are stripped on load. The one script kept is a scene (below), which is data, never code. - Comments are pins. A thread in
comments.jsonsits on an artboard (artboardId, plusnodeIdfor a node in it) or on a page's open canvas (pageId), atx/y. One missing a field isn't shown, and stays in the file until it's fixed. - Agents can read the format too.
get_guide('format')gives them every file's fields, with examples, for importing or generating a design by writing it directly. - Fonts just work. Google Fonts named in
font-familyload automatically on the canvas and in screenshots. - Examples stay off your home screen.
"example": trueindesign.jsonmarks a sample design. The home screen lists an example only once you open it yourself (from Finder, the app, orponzu serve path/to/example.design), not when it's merely found: by Spotlight, by a scan of your folders, or because an agent started in a folder that has one. Agents still open and use examples like any other design. The ones inexamples/are marked.
To implement a design, point your coding agent at the folder. The HTML, tokens and comments are the spec.
An artboard can play itself as a product demo: a cursor that moves, clicks and types, a toast that rises, a total that rolls. The artboard's HTML holds the finished screen, and one <script type="ponzu/act"> in it lists the beats:
<script type="ponzu/act">
click @Field
type @Email placeholder=@Placeholder
click @Invite
show @"New member" grow
& show @Toast rise
count @Seats from 4
poster
wait 2000
reset
</script>It never runs as code. Ponzu measures the board and turns each beat into Web Animations, so a scene pauses and seeks like any CSS animation: the canvas loops it, a screenshot shows its poster frame, and record films a storyboard with one frame per beat. Hover, press and focus come from the design's own :hover, :active and :focus rules and their transitions. get_guide('act') is the language; get_guide('product') is how to make a product shot worth looking at, adapted from Stageshow by Benji Taylor; examples/product-shots.design has three to start from.
A .design committed to a public repo opens on the canvas at ponzu.design/gh/<owner>/<repo>/<path/to/name.design>, read-only, with its comments, images and fonts: this repo's Kettle, for one. Add @<branch, tag or commit> to the repo for another ref, or leave off the path to list every design in the repo. Link it from your README with a badge labelled by the design's name; without ?label= it reads "open in | Ponzu":
[](https://ponzu.design/gh/<owner>/<repo>/<path/to/name.design>)Ponzu keeps a light and a dark preview of each design in its .ponzu/ folder (the picture Finder shows). Commit them, and a shared link's card shows the design itself.
Ponzu is a set of packages you can build into your own product:
| Package | What it does |
|---|---|
@ponzu/core |
HTML ↔ node tree, ops with inverses (undo, change feed), sanitizer |
@ponzu/store |
Opens a .design from any storage; diffs outside edits into ops |
@ponzu/tools |
The agent tools as transport-agnostic { name, schema, handler } |
@ponzu/mcp |
Those tools over MCP (stdio and streamable HTTP) |
@ponzu/render |
Screenshots, frame-by-frame animation recordings, motion loops and a lint pass. Uses Chromium, an Electron window, or your own renderer |
@ponzu/react |
<Canvas>. Renders artboards with the host page's own engine, in shadow roots |
@ponzu/server |
The local host behind ponzu serve |
On a big page the canvas draws each board as cheaply as it can be seen: boards drawn small show as pictures (their looping motion as short videos over them), images load as copies sized to how they're drawn, and boards off screen skip rendering. A source that serves pictures provides snapshotUrl (and loopsUrl); without it every board is live DOM. ponzu serve makes pictures, loops and image copies in the background and keeps them in ~/.ponzu/cache (PONZU_HOME moves it), trimmed to a few GB, least recently used first.
import { Canvas, MemorySource } from "@ponzu/react";
import "@ponzu/react/styles.css";
<Canvas source={new MemorySource(doc, (next) => save(next))} theme="light" />Use the tools without MCP, for example with the AI SDK or the Anthropic API:
import { tools, runTool, jsonSchema } from "@ponzu/tools";
const defs = tools.map((t) => ({ name: t.name, description: t.description, input_schema: jsonSchema(t) }));
const result = await runTool(tools.find((t) => t.name === "write_html")!, args, { ws, caller });About thirty tools in six groups. get_guide serves the house rules.
| Group | Tools |
|---|---|
| Orient | get_basic_info get_tree get_node get_html find_nodes |
| Write | create_artboard write_html update_styles set_text set_attributes duplicate move_nodes delete rename update_artboard |
| See | screenshot (with lint) record export |
| Organize | list_files create_file update_file create_page update_page archive get_tokens set_tokens |
| Converse | list_comments get_comment create_comment reply set_comment_status send_feedback |
| Listen + remember | wait_for_activity set_status write_note read_notes get_guide |
Guides cover the basics, layout, type, motion, product shots, scenes and a kit of windows and phones at real geometry, among others.
Skills for agents that support them ship in the plugin, in plugin/skills/.
The Mac app and the ponzu package send pseudonymous usage to the Ponzu team: which tools agents call and whether they fail (with a coarse reason, like a timeout), which agents connect, how often the canvas opens, and the Ponzu version and OS. Each install is a random id in ~/.ponzu/telemetry.json. When Tailscale is running, Ponzu also links that id to a hash of your Tailscale user id, which is the same on each of your Macs, so the team counts people rather than machines. Your Tailscale id, name and email are never sent, but someone with the hash could check guessed ids against it. Nothing about your designs leaves your machine: no HTML, file names, paths, comment text or tool arguments.
Agents can also tell us how Ponzu is working for them with send_feedback. That's the one event with words in it, and it's about Ponzu, not your work.
Turn all of it off with ponzu telemetry off, or set PONZU_TELEMETRY=0 or DO_NOT_TRACK=1. Nothing is sent in CI. Only release builds report: Ponzu built from source, or a fork, has no key and sends nothing. The packages you embed (@ponzu/tools, @ponzu/react and the rest) send nothing on their own; a host opts in by passing a telemetry to its workspace.
bun install
bun test
bun run --filter @ponzu/studio build
bun apps/cli/src/index.ts serve examples/halon.design examples/kettle.design examples/weft.designexamples/ holds three made-up companies to try Ponzu on: Halon (incident response for engineering teams), Kettle (an iPhone app for people who live together) and Weft (a sync platform for developers). Each has a brand, architecture diagrams, a website and an app, with its brief in notes/brief.md.
For studio work, run bun run dev: the studio with hot reload on all three examples.
Run from a checkout, serve stays out of the installed app's way: it listens on a port in 7800–7999 derived from the checkout's path (the same every run, different per worktree, the next free one if it's taken), and not on the tailnet, so your iPhone and other Macs keep finding the app, not a dev server. --port picks a port; --tailnet answers the phone and other Macs too, for testing against them (the app's tailnet port must be free). The studio's dev server proxies to the checkout's port; PONZU_SERVER=<url> points it elsewhere.
Desktop: cd apps/desktop && bun run start. iPhone: cd apps/mobile && bunx expo run:ios (docs/mobile.md).
PR titles are Conventional Commits, and merging a feat or fix ships a signed release on its own. See CONTRIBUTING.md.
Ponzu's own brand lives in this repo as .design folders under design/, and opens on the canvas in your browser:
- Brand: the slice, the lockup, the app icon, color, type, motion and voice.
- Press kit: a description and the facts, logos, product shots and social images, ready to export.
- Design system: the studio's bars, buttons, menus, comments and icons, in light and dark.
All three. Each one's notes/brief.md says where its source of truth lives in the code.
MIT