How to build a CleanShot alternative

How to build a CleanShot alternative by capturing a region, drawing an arrow, blurring a secret, and keeping a dated library in the browser. Prompts inside.

  • guide
  • vibe-coding
  • cleanshot

How to build a CleanShot alternative is a grab, a mark, and a file you can drop into a doc. CleanShot X is that product. The catalog line is “Screenshot, annotate, drag it where it needs to go.”

You pay $8–$10 a month. That is $96–$120 a year. The verdict is “Capture, annotate, drag, done”. The first estimate is 10 hours: capture, annotate, a floating thumbnail, and a library.

The audience is a Mac user who screenshots all day for docs and bug reports. If you live in scrolling capture, OCR, and a GIF export, keep paying. The same v1 also replaces Shottr and Monosnap when the job is “mark this and hand it over”. A gradient frame for social is the screenshot frame guide. The Shots.so post is that frame, at $10–$20 a month. Shrinking the file is TinyPNG Pro, $25–$39 a month. A wider crop-and-resize bench is Photoshop, $9.99–$22.99 a month, about 12 hours.

How to build a CleanShot alternative in one tab

The CleanShot X page splits the work into three rows.

The first row is the one you want. Capture a region, a window, or the full screen. Draw an arrow, a short label, and a blur over a secret. Copy the result, or save it with a filename you can predict. Keep a dated library on the machine. The catalog names the project Shotdock.

The second row is 3 days, 24 hours. Scrolling capture, OCR text copy, a self-timer, and backgrounds. The third row is 3 months and up, 480 hours. Screen recording, GIF export, and cloud sharing. The non-goal already names video, cloud uploads, team libraries, and a general photo editor.

If you tell an agent to “clone CleanShot”, it aims at the menu-bar app and the third row. Paste the prompts below, and keep each one on a region, a flattened PNG, and a library that never posts the bytes.

The catalog capability line is SwiftUI, ScreenCaptureKit, and a local disk, because a global hotkey and a thumbnail over every app are Mac APIs. The prompts on this site still ship in two flavors. This v1 runs in the browser, which is the stack those prompts use. A system-wide shortcut can wait.

Flavio grabs the pricing page. He crops the yearly toggle, draws an arrow, blurs [email protected] in the corner, and copies bug-2026-11-03-yearly-toggle.png.

Where the pixels live

Every product prompt ships in two flavors: Node.js plus Astro, and TanStack Start. This site is an Astro app on Cloudflare, so that is the path I default to. Use TanStack Start if React is your weekday language. The paint still runs in the tab. The stack post is which tab you copy. The prompt post is how you pin the first estimate. The scope post is why video waits.

Shotdock is one static page. Astro serves the HTML. There is no image route, no account, and no database on the server. Capture uses the display picker, and only after a click. A crop rectangle turns a window or a full screen into a region. Paste from the clipboard is the other door, for a shot the OS already copied.

Arrows, labels, and blur are drawn on a canvas in the page. Copy and download flatten those strokes into the pixels. IndexedDB holds the flattened PNG, the title, and the time. The filename template lives in localStorage. Nothing in this v1 runs on a schedule.

TanStack Start keeps the same page. Server functions stay unused for the image. If the agent adds a SQL table for captures, send it back to the tab.

BROWSER Paste, or capture on a click Flavio, pricing page STATIC ASTRO PAGE One HTML request No image upload route CANVAS Arrow, label, blur FLATTEN One PNG INDEXEDDB Dated library OUTSIDE V1 Menu bar, global hotkey Cloud, video, GIF
The tab captures and flattens. The server never sees the PNG.

The capture you keep

The card lists Capture, Annotation, LibraryEntry, and NamingTemplate. This v1 needs a capture and a template. An annotation exists only until you copy or save. After that, the library entry is the flattened file.

A capture has a stable id, a title, a created time, a width, a height, and the PNG bytes. The title starts as “Yearly toggle”. The id does not change when you rename it. Delete removes that id from IndexedDB. There is no trash and no cloud copy.

The template is one pattern, bug-{date}-{title}.png. Date is the local calendar day, 2026-11-03. The title is lowercased, spaces become hyphens, and anything that is not a letter or a digit drops out. The download name for this shot is bug-2026-11-03-yearly-toggle.png.

Here is the shape after the blur is flattened. The bytes are the picture. The arrow is already inside them.

const capture = {
  id: 'cap-yearly-toggle',
  title: 'Yearly toggle',
  createdAt: '2026-11-03T09:40:00.000Z',
}

The guardrail on the card is the rule that matters. Keep every capture on the machine. Ask for screen capture only from the button, and say why. Flatten the blur so a later open cannot peel it off.

TEMPLATE bug-{date}-{title}.png CAPTURE Yearly toggle 3 Nov, one PNG FLATTENED PIXELS Blur is part of the file LAYERS Gone after save CLOUD COPY Not in v1
One id, one flattened file. The template only names the download.

The screens on the desk

Three surfaces, all on /. Keep them boxes and labels. If the agent starts a photo editor, send it back to these boxes.

The desk explains the next click. Capture asks for a window or a screen, then you drag a rectangle. Paste reads an image from the clipboard. Drop a PNG on the same area. Cancel the picker and the library stays as it was. A denied permission shows a sentence on the page and writes nothing.

CAPTURE Local Next click asks for a window Capture Paste Drop a PNG Filename bug-{date}-{title}.png
The picker waits for Capture. Paste and drop skip the picker.

The markup view shows the crop, a title field, and three tools: Arrow, Text, and Blur. Arrow is a line with a head. Text is one short label, “Yearly price”. Blur covers a rectangle and replaces those pixels with a blocky average. Undo removes the last stroke while you are still on this view. Copy, Save, and Discard sit under the canvas. A small thumbnail of the current flatten sits in the corner so you can drag that PNG into another browser tab.

MARKUP Yearly toggle Crop plus arrow Blur box on the email Thumb Arrow Text Blur Copy Save Discard Undo last stroke
Copy and Save bake the blur. Discard writes nothing.

The library lists captures newest first, grouped by day. Each row shows the thumbnail, the title, and the time. A search box matches the title. Open loads the flattened PNG onto the desk as a new capture, so a second blur does not edit the stored file in place. Delete asks once, then the row is gone. Empty state says to capture or paste. There is no account menu.

The path from a grab to a file

One shot moves through states you can point at. Nothing is stored until Save or Copy. Discard drops the canvas.

1 · CLICK CAPTURE Picker opens from the button CANCEL Library unchanged GRANT WINDOW Then crop 2 · ARROW AND BLUR Strokes stay editable 3 · COPY OR SAVE PNG enters the library DISCARD No row 4 · LIBRARY Newest day first, search by title 5 · DELETE That id leaves IndexedDB Reload keeps the saved PNG
Cancel and Discard write nothing. Save stores pixels, then the strokes are gone.
  1. Flavio opens the desk. The picker stays closed. He presses Capture, picks the pricing window, and drags a rectangle around the yearly toggle.
  2. He adds an arrow and the label “Yearly price”. He blurs the email in the corner. Undo removes the blur. He draws the blur again.
  3. He presses Save. The library shows “Yearly toggle” under 3 Nov. The download name is bug-2026-11-03-yearly-toggle.png.
  4. He drags the thumbnail into a doc in another tab. The dropped file is the flattened PNG.
  5. He opens the row. The desk shows the picture. The arrow is not a separate object he can move.
  6. He deletes the row and reloads. The title is gone.

Prompt 1. A desk that stores nothing yet

Build Shotdock, a local
screenshot desk for one
person.

Use Node.js and Astro.
One server-rendered page
at /. Ordinary HTML.
If you use TanStack Start
instead, same page, same
rules. Do not add a SQL
table for images.

Explain that Capture will
ask for a window or a
screen. Paste reads an
image from the clipboard.
Dropping a PNG on the
desk loads it too.

Show the filename pattern
bug-{date}-{title}.png.
Save that pattern in
localStorage when it
changes.

No account. No upload
route. No gradient frame.
No device mock.

Done when a reload shows
the pattern you typed,
and the network log has
no image request.

What to check after the empty desk

Load /. Capture, Paste, and a drop area are visible. The picker does not open by itself.

Change the pattern, reload, and read it back. In the network panel, confirm the document is the only request that matters. There is no POST of a file.

Prompt 2. Capture a region from a click

Wire Capture to the
display picker, and only
from that button. If the
user cancels or denies
permission, write no
capture and show a short
reason on the page.

After a grant, let the
user drag a rectangle.
That crop is the region.
Skipping the drag keeps
the whole frame, which
covers a full screen or
a whole window.

Paste and drop land on
the same crop step.
A cancelled crop returns
to the empty desk.

No scrolling capture.
No self-timer. No global
hotkey.

Done when cancelling the
picker leaves the page
empty, and a crop shows
only the rectangle.

What to check after a grab

Press Capture, then cancel. The desk is empty and the library count is unchanged.

Grant a window, drag a tight rectangle, and confirm the canvas matches that rectangle. Paste a copied screenshot and confirm you can crop that too.

Prompt 3. Arrow, text, and a blur that sticks

On the crop, offer Arrow,
Text, and Blur. Arrow is
a line with a head. Text
is one short label. Blur
covers a rectangle by
replacing those pixels
with a blocky average.

Undo drops the last
stroke. Discard drops
the canvas and writes
nothing.

Copy and Save flatten
the image, the strokes,
and the blur into one
PNG. The library stores
that PNG only. A later
open cannot move the
arrow or clear the blur.

The download name comes
from the pattern. Title
"Yearly toggle" on
3 Nov 2026 becomes
bug-2026-11-03-yearly-toggle.png.
Strip characters that
are not letters, digits,
or hyphens.

Show a thumbnail of the
flattened PNG. Dragging
it starts a file drag
of that PNG.

Done when you open the
saved file in another
app and the email under
the blur is not readable.

What to check after the blur

Draw an arrow, a label, and a blur. Undo once and confirm the blur disappears, then draw it again.

Save. Open the PNG outside the app. The arrow is visible and the blurred region is blocks, with no original text showing through. Copy puts that same picture on the clipboard.

Drag the thumbnail into another tab. The dropped file matches the download.

Prompt 4. A library grouped by day

Add a library on the
same page. Group rows
by local calendar day,
newest day first. Each
row shows a thumbnail,
the title, and the time.

Search filters by title.
Empty state tells you
to capture or paste.

Save and Copy insert
one row for that id.
Open copies the stored
PNG onto the desk as a
new capture. It does
not revive strokes.

Delete asks once, then
removes that id from
IndexedDB. Reload must
not bring it back.

No accounts. No share
link. No cloud backup.

Done when a saved shot
survives a reload, a
search finds the title,
and delete plus reload
leaves the list empty.

What to check after the library

Save two shots with different titles on the same day. Both appear under that day. Search for “Yearly” and see one row.

Open a row, add a second arrow, and save. You now have two library rows. The first file is unchanged.

Delete one, reload, and confirm it stays gone. The other row is still there.

Failures the agent ships anyway

A picker on page load

The permission dialog appears as soon as / renders. Browsers also block a capture that did not start from a gesture, so the button then does nothing. Move the call onto Capture.

A blur that is only a box

The canvas shows a gray rectangle, and the downloaded PNG shows the email in clear type. The export drew the photo and skipped the blur pass. Bake the blocky pixels into the bitmap before toBlob.

A library that keeps layers

You reopen “Yearly toggle” and the arrow is still a handle. The store kept the original plus a stroke list, so anyone with the database can hide the blur. Store the flattened PNG. Open starts a new capture from those pixels.

An upload “for backup”

The network panel shows a POST, or an r2.dev host, after Save. The hosting post is for products that need a server. This one does not. Delete the route.

A frame tool in the same page

Padding sliders, a gradient, and a device chrome mean the agent drifted into Shots.so. Shotdock’s job is the mark and the file. Backgrounds are the 24-hour row.

A filename with a slash

The title Q3/yearly downloads as a nested path or a blank name. The template step has to strip everything outside letters, digits, and hyphens before it builds the name.

Deploy the desk

Shotdock is a static Astro site. It does not need D1, R2, or a cron. Put it on a subdomain you already own, such as shot.flaviocopes.com.

The hosting post still covers the adapter and the custom domain. Skip the bindings section.

Open the deployed page, capture a window, blur one secret, and save. Reload. The row is there. In the network panel, confirm Save did not send the image. Delete the row and reload again.

Use it for a week of bug notes. If you miss a global hotkey, scrolling capture, or OCR, those are the later rows. Then cancel CleanShot, or keep paying if the thumbnail has to float over apps that are not a browser.

What to skip on purpose

Leave out screen recording, GIF export, cloud sharing, and a team library.

10-HOUR V1 Capture a region on a click Arrow, label, and a real blur A dated library in the tab SKIP THIS HALF Scrolling capture and OCR A menu-bar hotkey Video, GIF, and cloud
Ship the flattened PNG. Leave the recorder on the skip row.

Scrolling capture, OCR, a self-timer, and backgrounds wait until the blur already survives a trip through Preview or Slack. A menu-bar app is a different runtime. Build it when you miss a hotkey, and keep the same flatten rule.

Start now

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

Tell the agent the first estimate only: capture, annotate, a thumbnail, and a library. Ten hours.

Blur one real secret and open the file somewhere else before you add a hotkey.