Click. Comment. Feed to AI.
Ultra-lightweight (15KB) tool to annotate any webpage and export comments for your AI coding assistant. One script tag. Zero dependencies.
Add one line to your HTML:
<script src="https://unpkg.com/@kibkalo/tack"></script>Then add #tack to your URL:
https://yoursite.com/page#tack
That's it. Click elements, leave notes, export for AI.
- Add the script — stays dormant until activated
- Add
#tackto URL — toolbar appears, no page reload needed - Click any element — leave a note, or rewrite its text directly
- Copy — paste into ChatGPT, Claude, Cursor, etc.
- Check what was applied — reload afterwards and see what actually landed
Copy exports this page's notes and clears them. You handed them to the AI; they shouldn't come back in the next export. Undo is one click, and ☰ → Restore last export brings back the whole batch if you need it.
Reviewing several pages? Notes accumulate per page as you navigate. ☰ → Copy all pages bundles everything into one file, grouped by URL. ↓ Download .md never clears anything.
Click a text element and its current text is already in the edit box. Change it, and the export carries an exact replacement rather than a description:
**Current:** `Building the future, one widget at a time.`
**Change to:** `Ship your first widget in five minutes.`Works on alt, placeholder, title, aria-label, href and value too. The export tells the model to apply these verbatim, and to stop if the current text no longer matches what you saw.
Open ◨ Style on an element and change its size, line height, weight, colour, background, padding, gap or radius. The page updates as you type; save, and it goes back to how it was. The note carries the before and after, not the change.
**Style:**
- `font-size`: `17px` → `27px`
- `color`: `rgb(102, 102, 102)` → `rgb(17, 17, 17)`Values are the browser's computed styles, so the note is exact and the applied check can verify it properly. The export tells your agent to apply the change where the style is authored and keep whatever the codebase already uses — 16px → 24px must not turn var(--space-4) into a pixel literal.
The preview is a paused Web Animation held at its last frame: it beats the author cascade and inline styles, reaches into open shadow roots, and leaves no attribute or inline style behind. It cannot beat !important — the row says so rather than pretending, and the note still records what you asked for.
☰ → Copy review link compresses the whole review into the URL:
https://acme.com/pricing#tack=zVY9b8IwEP0rlmdESttD…
Open it anywhere and the notes reappear on the real page. No account, no backend, no install on the other end — the notes travel in the link itself.
After your agent finishes, reload and pick ☰ → Check what was applied. Tack compares each annotated element against what it looked like when you flagged it: changed elements go green, unchanged stay amber, missing ones are called out. Text edits are matched exactly against what you typed.
Shift-click several elements, or drag a box around them, then write one note for the group ("these three cards should be equal height"). The export lists every selector the note applies to.
Not all feedback is about an element. Drag a box over whitespace, a gap, or a strip that feels cramped, and Tack records the area itself rather than doing nothing.
The note carries the box in page coordinates, the deepest element containing it under **Inside:**, and what falls inside under **Covers:** — and the export tells the agent to work out which of those the note is really about. Region notes travel in a review link, and are skipped by the applied check because an area has no expected value to compare against.
Some dev servers stamp the source file onto the DOM. Where one is present Tack reads it and puts it above the selector as **Source:**:
| Attribute | Emitted by |
|---|---|
data-astro-source-file + data-astro-source-loc |
Astro, automatically in dev |
data-inspector-relative-path + -line + -column |
react-dev-inspector |
data-v-inspector |
vite-plugin-vue-inspector |
data-tsd-source |
TanStack Devtools |
data-component-path + data-component-line |
Lovable-style tooling |
Astro does this by default in development; React and Vue only with their inspector plugins installed, so expect it to be absent more often than present. It stays a hint — the export tells the agent to confirm the path exists before editing it. These paths can be absolute and carry a username, so they are kept out of review links.
For pages you don't control, use the bookmarklet from the landing page. Drag it to your bookmarks bar, click on any page. Sites with a strict script-src CSP will block it.
Tack exports Markdown that tells the model how to read it:
# Tack review — 2 notes
https://acme.com/pricing · viewport 1440×900 · 2026-08-05
## How to use this file
Each note has three anchors, most reliable first:
1. **Text** — element text at review time. Grep this first.
2. **Section** — nearest preceding heading. Disambiguates repeated text.
3. **Selector** — DOM path at review time; exact but stale after a refactor.
Resolve by Text, confirm with Section, fall back to Selector. If nothing matches,
the markup changed: act on the note's intent, do not guess a nearby element…
---
## 1.
**Where:** section "Pricing" → `button`
**Text:** `Buy now`
**Classes:** `btn btn-primary`
**Selector:** `[data-testid="cta"]`
**Note:**
> Too aggressive. Use "Start free trial" and make it green.Selectors break on the first refactor; element text usually survives. Telling the model that up front is worth more than another field of metadata.
- 📦 15KB gzipped — zero dependencies, vanilla JS
- 🔒 Local-only — no data leaves your browser (localStorage)
- 👻 Dormant until needed — activate with
#tack - 🖐 Never rewrites your elements — the UI is one shadow-root container on
<body>; marks are drawn over the page, so<img>, inputs, SVG and tables are untouched. Freeze and style preview change how the page looks while on, and revert when off - ✏️ Rewrite text and attributes in place instead of describing the change
- 🔗 Share a review as a link — no server, no account
- ✅ Verify what was applied after the agent runs
- 🌓 Shadow DOM & SPA aware — selectors cross open shadow roots (
>>>), pins survivepushStatenavigation - 📱 Stays on the screen — the toolbar and popups are pinned to the visible viewport, not the layout viewport, so they remain reachable on a phone even when the host page is wider than the device
- ✍️ Text selection — select a phrase to annotate exactly that
- 🎯 Multi-select — shift-click or drag a box, one note for many elements
- ⏸ Freeze animations so you can annotate a specific frame
- 🌗 Light and dark toolbar
- 📄 Multi-page — per-page by default, all pages on demand
- 🌐 Any page, any framework — bookmarklet for sites you don't control (a strict
script-srcCSP will block it)
⌘⇧F / Ctrl⇧F |
toggle Tack |
⌘↵ |
save note |
↑ / ↓ |
move the target to the parent / first child element |
Enter |
annotate the element you moved to |
Esc |
close popup, or drop the current selection |
Deliberately no single-letter shortcuts — Tack runs on top of your app, and your app's shortcuts win.
Available as window.__tack once the script loads:
__tack.on() // activate without the #tack hash
__tack.add('#hero h1', 'note') // annotate by selector or element
__tack.add('#sub', '', {to: 'New copy'}) // propose exact replacement text
__tack.add('#img', '', {a: 'alt', to: 'Alt'}) // …or an attribute value
__tack.add('#h', '', null, {'font-size': '24px'}) // …or a style
__tack.menu() // open the actions menu
__tack.open('#hero h1', {edit: 1}) // open the editor on an element
__tack.select(['#a', '#b']) // stage a multi-element selection
__tack.region({x,y,w,h}, 'note') // annotate an area, not an element
__tack.list() // all notes as plain objects
__tack.md(true) // markdown for every page (false = current page)
__tack.copy(false) // copy current page's notes, then clear them
__tack.link() // shareable URL with the review inside
__tack.load(url) // import a review link
__tack.applied() // [{note, status: 'ok' | 'no'}]
__tack.prefs({light: 1}) // block, markers, freeze, light, open
__tack.off()This is how an agent with browser control reviews a page it doesn't own: load the URL, inject tack.js, annotate, read md().
Tack reports what a reviewer did as plain DOM events on window, so a host page can measure the flow without the library ever making a request. Nothing listens by default.
addEventListener('tack:export', e => {
analytics.track('review handed over', {notes: e.detail.notes})
})| Event | event.detail |
|---|---|
tack:activate |
{notes} — switched on, with this many notes already on the page |
tack:note |
{here, total, edit, style, multi, region, source} — a note was saved; edit for a rewrite, style counts adjusted properties, region for an area, source when a file hint was found |
tack:export |
{notes, all} — Copy for AI handed the notes over and cleared them |
tack:download |
{notes, all} — saved as a .md file |
tack:share |
{notes, chars} — a review link was produced |
tack:verify |
{checked, applied, missing, skipped} — the applied check ran; skipped counts region notes |
tack:import |
{notes} — a review link was opened and loaded |
Payloads are counts and flags only — never note bodies, selectors or page text. A listener will often forward these somewhere, and nothing a reviewer typed should be able to leave that way.
Until #tack is in the URL hash, Tack registers one idle keyboard listener and stops: no toolbar, no elements added, no network requests, nothing read from the page.
Once active it is honest about what it does: a floating toolbar, intercepted clicks and hovers, and an overlay above your page. Closing it removes every trace.
The library sends nothing anywhere — no telemetry, no version check, no error reporting. It makes no network requests at all; notes live in localStorage and a review link carries its payload in the URL fragment, which browsers never send to a server. A test drives the whole product with fetch, XMLHttpRequest, sendBeacon, Image, WebSocket and EventSource instrumented, and fails if any of them fires.
The gettack.dev website uses aggregate visitor analytics — details.
skills/tack/SKILL.md covers both installing Tack in a project and applying a pasted review. Point your agent at it, or copy it into .claude/skills/.
Full documentation lives at gettack.dev/docs.
npm install
npm test # headless Chrome smoke suite, source + built artifact
npm run test:site # build the site, then check metadata, links, sitemap, robots
npm run build:js # tack.js → tack.min.js
npm run dev # landing pagetack.js is the readable source. tack.min.js is what npm and unpkg serve; it is committed, so rebuild and commit it whenever tack.js changes.
| Tack | Agentation | Vibe Annotations | Drawbridge | |
|---|---|---|---|---|
| Install | 1 script tag | npm + edit your app | Extension + MCP | Extension |
| Framework | Any | React 18+ only | Any | Any |
| Build step | None | Required | None | None |
| Pages you don't own | ✓ (bookmarklet) | ✗ | ✗ localhost only | ✓ |
| Ship to production | ✓ dormant | ✗ dev-only | ✗ | ✗ |
| Size (gzip) | 15 KB | 115 KB | Extension + server | Extension |
| License | MIT | PolyForm Shield | MIT | Custom |
| Agent sync loop (MCP) | ✗ not yet | ✓ | ✓ | ✗ |
| Component source file | ✗ | ✓ React | ✓ React/Vue | ✗ |
| Layout / wireframe mode | ✗ | ✓ | ✗ | ✗ |
Compared against agentation@3.0.2 on 2026-08-05, from its published npm package and docs; the Vibe Annotations column was re-checked against its own site and repository on 2026-08-06, correcting two rows that were wrong in our favour. Sizes are gzip of the shipped bundle (gzip -c -9 on tack.min.js and on their dist/index.mjs). A ✗ means "not documented", not "impossible".
MIT — kibkalo.com
