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.
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.
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.
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.
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.
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.
- Flavio opens the desk. The picker stays closed. He presses Capture, picks the pricing window, and drags a rectangle around the yearly toggle.
- 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.
- He presses Save. The library shows “Yearly toggle” under 3 Nov. The download name is
bug-2026-11-03-yearly-toggle.png. - He drags the thumbnail into a doc in another tab. The dropped file is the flattened PNG.
- He opens the row. The desk shows the picture. The arrow is not a separate object he can move.
- 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.
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.