How to build a Miro alternative

How to build a Miro alternative with sticky notes, pan and zoom, and a board that saves each drag. Follow the prompts and skip the live room.

  • guide
  • vibe-coding
  • miro

How to build a Miro alternative is sticky notes on a board you can pan and zoom. Miro is that product. The catalog calls it “Sticky notes, voting dots, and one infinite-ish canvas.”

You pay $10–$20 a person a month. That is $120–$240 a year per person, and the price scales with the team. The verdict is “For workshops, absolutely”. The first estimate is 12 hours: canvas, sticky notes, pan and zoom.

The Miro teardown is the live board, the owner process, and the cursors. This guide is the 12-hour screens, rules, and prompts. Voting dots wait until the notes already stay where you left them.

The audience is a facilitator running a remote retro. If the company already runs workshops with a few hundred people on one canvas, keep paying. The same v1 also replaces Mural, FigJam, and Lucidspark when the job is notes on a board. A wireframe is Figma, $16–$55 a person a month, about 4 days, and the Figma post is why that file is a different product. Boxes and arrows are Lucidchart, $9–$11 a person a month, about 10 hours. Columns of cards are the Trello guide.

How to build a Miro alternative as notes on a board

The Miro page splits the work into three rows.

The first row is the one you want. A canvas. Colored notes. Pan and zoom. The notes survive a reload. That is the 12-hour v1. The catalog names the project Stickywall.

The second row is 1 week, 40 hours. Live cursors, dot voting, frames, and exports. The third row is 3 months and up, 480 hours. Freehand drawing, diagrams, and hundreds of simultaneous editors. The non-goal already names drawing, embeds, a template marketplace, and that crowd.

If you tell an agent to “clone Miro”, it aims at the third row. Paste the prompts below, and keep each one on notes, positions, and a reload.

Flavio creates a board called Friday retro. He adds “Ship the notes”, “Drop the standup”, and “Try pair writing”. Jordan is the other member. Jordan opens the same board, moves a note, and reloads. The note is still there. A signed-out visitor gets a login wall.

The parts and where a note is saved

Every product prompt ships in two flavors: Node.js plus Astro, and TanStack Start. The capability line for Miro is Astro, Alpine, and Durable Objects. Durable Objects are the live room. The hosting post says they wait until a second cursor has to move on the same board. This v1 has no room. Alpine pans, zooms, and drags in the browser. D1 stores the notes. Use TanStack Start if React is your weekday language, with server functions and a SQL database with migrations. The stack post is which tab you copy. The prompt post is how you pin the first estimate.

A save is a request at the end of a drag, or when the text changes. The teardown says to persist that write and to skip every pointer move along the way. Pan and zoom stay in the browser. They are a view. They are not rows.

The server checks that the member belongs to the workspace, then checks the text, the color, the coordinates, and the revision. A stale revision keeps the stored note. There is no cron and no websocket.

BROWSER WITH ALPINE Pan, zoom, drag a note Flavio or Jordan ASTRO ON DRAG END Check member and revision Write x and y once D1 HOLDS THE BOARD Notes, color, x, y Revision on each save RELOAD Same notes, view reset OUTSIDE V1 Cursors, votes, drawing
The view lives in the browser. The note lives in D1 after the drag ends.

The data one note carries

The card lists Board, Participant, Frame, Note, and Vote. This v1 needs the board, the members, and the note. Frame and Vote wait for the 40-hour row. A participant here is a workspace member. There is no anonymous mode yet.

Stickywall has one workspace. Flavio and Jordan are members. Seed passwords come from the environment, and the database stores hashes. The session cookie is httpOnly.

A board carries a title. Friday retro is the one you test. A second board, Planning, exists so you can prove notes stay on the board that owns them.

A note carries a stable id, the board, the text, a color, an x, a y, and a revision. The teardown describes the coordinate rule from their board model. x and y are the center of the note, relative to the center of the board. That center is 0, 0. A new note lands on the origin.

Colors are a fixed list: yellow, blue, coral, lime. Text stops at 280 characters. The board stops at 200 notes. The teardown cites their API limit of 5,000 children inside a frame. Two hundred is the cap for this retro, and the server enforces it.

A move or an edit posts the revision the page loaded. If D1 has a newer revision, the write is refused and the response shows the stored text and position. The accepted write adds one to the revision.

Here is “Ship the notes” after Jordan drags it.

const note = {
  id: 'note-ship',
  boardId: 'board-friday-retro',
  text: 'Ship the notes',
  color: 'yellow',
  x: 120,
  y: -40,
  revision: 2,
}

Pan and zoom are absent from that object on purpose. Reload opens the view at the origin. The note is still at 120, -40.

STICKYWALL Flavio, Jordan FRIDAY RETRO Three notes PLANNING Separate notes SHIP THE NOTES 120, -40 · rev 2 DROP THE STANDUP yellow's neighbor TRY PAIR WRITING coral note FRAME Later VOTE Later CENTER IS 0, 0 x and y mark the note center
Notes belong to one board. The view you panned is not stored.

The screens you ship

Three surfaces. Keep them boxes and labels. If the agent starts a pen tool or a cursor with your name on it, send it back to these boxes.

The board list is /. It shows Friday retro and Planning, with a note count on each. New board asks for a title. Empty state says to create a board. There is no template gallery.

BOARDS Flavio Friday retro 3 notes Planning 0 notes New board
The list is the navigation. Each count belongs to that board only.

The canvas is /boards/board-friday-retro. The header has the title, Add note, Zoom in, Zoom out, and Reset. Reset returns the view to the origin. Dragging the background pans. Dragging a note moves that note, and the save fires when you let go. Notes show their text and use the four colors. A selected note shows Edit and Delete.

FRIDAY RETRO Add note Zoom + Zoom - Reset Ship the notes 120, -40 Drop the standup Try pair writing Drag the background to pan
The numbers under a note are board coordinates. Zoom does not change them.

The note editor opens from Add note or from Edit. It has the text, the four colors, and Save. A new note previews at the origin. Delete sits on an existing note and asks you to confirm. Over the cap, or over 280 characters, the form names the rule and writes nothing.

NOTE Rev 2 Ship the notes Yellow Blue Coral Lime Save Delete
Save posts the revision in the header. A newer row on the server wins.

The path from a blank board to a reload

One note moves through states you can point at: absent, at the origin, moved, or deleted. A stale drag loses.

1 · CREATE THE BOARD Friday retro, zero notes 2 · ADD AT THE ORIGIN Ship the notes, x 0 y 0 3 · DRAG END Save 120, -40 Revision becomes 2 STALE Keep rev 2 4 · JORDAN RELOADS Same note, view at 0, 0 NOTE 201 Refused, cap 200 DELETE Gone on reload PLANNING STAYS EMPTY Retro notes never appear there SIGNED-OUT VISITOR Login wall, no note text
Create, place, reload. A stale save and a 201st note both leave the board as it was.
  1. Flavio signs in and creates Friday retro. The canvas is empty and offers Add note. He also creates Planning and leaves it empty.
  2. He adds “Ship the notes” in yellow. It sits at 0, 0. He adds the other two notes and drags them apart.
  3. He lets go. One request stores the new center. The network panel shows that single write, not a stream of moves.
  4. He zooms in and pans away from the origin. Reset brings the view back. The stored coordinates stay the same.
  5. Jordan opens Friday retro and reloads after Flavio’s drag. He sees the same three notes. Planning still shows zero.
  6. Jordan’s tab still has revision 1 and he saves a move. The board keeps revision 2 and shows Flavio’s position.
  7. Delete removes “Drop the standup”. Reload confirms it is gone. The other two notes stay.

Prompt 1. Seed the team and the board list

Build Stickywall, a workshop
board for one team.

Use Node.js and Astro.
Server-rendered pages.
Ordinary HTML forms.
Cloudflare D1 for the rows.
Use Alpine on the canvas
for pan, zoom, and drag.
If you use TanStack Start
instead, use server functions
and a SQL database with
migrations. Same screens.
Same rules.

Seed one workspace. Flavio
and Jordan are members.
Both sign in with a session
cookie. Passwords live in
env, not in the repo.

Every page and every
mutation checks membership
on the server. A signed-out
visitor gets a login wall.
A guessed board URL with
no session returns nothing
about the notes.

The list at / shows each
board title and its note
count. New board asks for
a title. Empty state tells
you to create a board.

No templates. No billing.
No websocket.

Done when both members
can sign in, create Friday
retro, and a private window
cannot read the list.

What to check after the list exists

Sign in as Flavio. Sign in as Jordan in another browser. Both see the same boards.

Create Friday retro and Planning. Each shows 0 notes. Open a board URL with no cookie. You get the login wall.

Prompt 2. Put a note on the origin

The coordinate rule is the one in the teardown. The center of the board is 0, 0. The center of the note is what you store.

Add the canvas at
/boards/:id and the note
editor.

Load only the notes for
that board. Friday retro
must not show Planning's
notes.

Add note asks for text and
a color. Colors are yellow,
blue, coral, and lime.
Anything else is refused.
Text over 280 characters
is refused, and the board
is unchanged.

A new note is stored at
x 0, y 0, revision 1.
Show it at the origin.
The canvas has Zoom in,
Zoom out, and Reset.
Reset returns the view
to the origin. Pan by
dragging the background.
Pan and zoom are view
state. Do not write them
to D1.

Cap the board at 200
notes. The 201st create
is refused.

No frames. No pen tool.

Done when three notes
appear on Friday retro,
Planning still shows 0,
and a reload keeps the
notes at the origin.

What to check after the first note

Add “Ship the notes” in yellow. Reload. It is still there, and Reset leaves it in view.

Add a 281-character text. The form explains the limit and the note count stays the same.

Open Planning. It is empty. A guessed color in the form post is refused.

Prompt 3. Save the drag in board coordinates

This is the bug the teardown is warning you about. A zoomed canvas will lie if you store screen pixels.

Dragging a note updates
its position in the view
immediately. Save once,
when the pointer goes up.
Do not send a request on
every pointer move.

Convert the drop point to
board coordinates. x and y
are the center of the note
relative to the board
center, with zoom and pan
removed. Zooming in and
dragging must still store
the board point, not the
pixel offset on screen.

Post the revision the page
loaded. If D1 has a newer
revision, keep the stored
text, color, and position,
and show those. An accepted
save adds one to the
revision.

Both members may move any
note on a board they can
open. The check is
membership, on the server.

Done when a zoomed drag of
"Ship the notes" reloads
at the same board point,
and an old revision does
not overwrite it.

What to check after a drag

Zoom in, pan away from the origin, and drag “Ship the notes”. Let go. The network panel shows one write. Reload, then press Reset. The note sits where you dropped it, and the view is back at the origin.

Leave Jordan’s tab on revision 1. Move the note as Flavio. Save from Jordan’s old tab. The position stays on Flavio’s revision.

Prompt 4. Edit, delete, and keep the cap

Edit opens the same note
form with the current text,
color, and revision. Save
follows the revision rule
from the drag.

Delete asks for a confirm,
then removes that note.
Reload shows the other
notes still in place.
Deleting on Friday retro
does not change Planning.

The 200-note cap is
checked on create inside
the same transaction as
the insert. Two creates
that would pass 200 leave
the board at 200.

Sanitize the text. A
pasted script must not
run. Show the text as
text.

No votes. No timer. No
export. No live cursors.
No Durable Object.

Done when Jordan can
recolor a note, delete
one, and a private window
still learns nothing
from the board URL.

What to check after a delete

Recolor “Try pair writing” to coral. Reload as Flavio. The color stuck, and the revision went up.

Delete “Drop the standup”. Reload. Two notes remain. Planning’s count is still 0.

View source. The note text is text. A pasted script tag does not run.

Open the board signed out. The login wall has no note text in the HTML.

Failures the agent ships anyway

Positions that vanish on reload

You drag three notes, refresh, and they stack on the origin again. The drop never reached the server, or the client drew from memory and ignored the row. The reload is the test. If Reset and a fresh browser disagree, the row is wrong.

Screen pixels saved as x and y

Zoom to double size, drag a note a short distance, and reload. It jumps far from where you dropped it. The write used CSS pixels after the zoom. Store the board center. The teardown’s coordinate note is the spec.

A request for every pixel

The network panel fills while the pointer is still down. That is the live-cursor product, started early. One write on pointerup is the 12-hour rule. The hosting post is where a Durable Object enters, and it enters with the 40-hour row.

Notes that leak across boards

Planning shows “Ship the notes”, or a count of 3. The query forgot the board id. Create, list, move, and delete all take the board id from the URL and check it on the server.

A pen, a vote, or a cursor

A draw tool, a dot-vote button, a frame called Keep, or a named cursor means the agent left the first estimate. Frames, votes, and cursors are the 40-hour row. Drawing and a crowd of editors are the 480-hour row.

A stale tab that wins

Jordan’s old screen overwrites Flavio’s move and the revision stays put. The save has to compare the posted revision with the row and keep the newer one. Show that row back on Jordan’s page.

Deploy the board

Stickywall wants Astro, Alpine, and D1. Put it on a subdomain you already own, board.flaviocopes.com. Nothing in this v1 runs on a schedule, and nothing needs a Durable Object binding yet.

The hosting post is the adapter, then the D1 binding, then the custom domain.

Attach the domain. Create Friday retro on your laptop. Add a note and drag it. Open the board on your phone as Jordan and reload. Confirm the note is there. Delete it on the laptop. Reload the phone.

Take a D1 backup before you delete a paid Miro workspace. The notes are the rows you will miss.

Use it for one real retro. If you miss a cursor, a vote, or a frame called Keep, those are the 40-hour row. Then cancel Miro, or keep paying if the room has to stay live while people talk.

What to skip on purpose

Leave out freehand drawing, diagrams, a template marketplace, and hundreds of simultaneous editors.

12-HOUR V1 Notes on one board Pan and zoom as a view Save position on drag end SKIP THIS HALF Live cursors and votes Frames and exports Drawing and a huge room
Ship the 12-hour board. Leave the live room on the skip row.

Live cursors, dot voting, frames, and exports wait until two people have already lost a note to a reload. The second mini-prompt on the card lives there. A timer and anonymous mode live there too.

More writeups land on the guides index. The audit is the catalog if the next ship should be a flowchart or a column board.

Start now

Go to Build your own Miro. Copy the Astro prompt, or TanStack Start if that is what you write.

Tell the agent the first estimate only: canvas, sticky notes, pan and zoom. Twelve hours.

Run Friday retro on the board once before you add a cursor.

Related articles

Read all articles →