A lightweight TypeScript library that overlays comment bubbles over any page. Add, edit, delete, persist, and export comments without wiring a UI.
- Add, edit, delete, and drag comments on any page
- Persist to localStorage (or keep in-memory)
- Export comments as JSON or Markdown
- Built-in overlay and optional drop-in controls (Export / Clear / Show/Hide)
- Keyboard: Shift+C to show/hide overlay (built-in)
- TypeScript types included
npm install prototype-comments
# or
yarn add prototype-comments
# or
pnpm add prototype-commentsimport { enableComments, commentManager, exportComments, mountCommentControls } from 'prototype-comments';
// 1) Enable comments (persist to localStorage; export as markdown or json)
enableComments({ storage: 'localStorage', exportFormat: 'markdown' });
// 2) Optional: mount drop-in controls (Add/Export/Clear/Show-Hide)
const unmount = mountCommentControls();
// Later: unmount();
// 3) Programmatic add at coordinates (page x/y)
commentManager.addComment({ text: 'Hello', x: 120 + window.scrollX, y: 200 + window.scrollY });
// 4) Export
const data = exportComments(); // string (markdown) or JSON arrayimport { mountCommentControls } from 'prototype-comments';
// After enableComments():
const unmount = mountCommentControls();
// Call unmount() to remove controlsimport { enableComments, disableComments, isCommentsEnabled } from 'prototype-comments';
const btn = document.getElementById('enable-disable') as HTMLButtonElement;
function render() { btn.textContent = isCommentsEnabled() ? 'Disable comments' : 'Enable comments'; }
btn.addEventListener('click', () => {
if (isCommentsEnabled()) disableComments();
else enableComments({ storage: 'localStorage', exportFormat: 'markdown' });
render();
});
render();// Set CSS custom properties on the overlay to change bubble width
const overlay = document.querySelector('[data-prototype-comments-overlay]') as HTMLDivElement;
// Minimum width (default 250px)
overlay?.style.setProperty('--prototype-comments-min-width', '520px');
overlay?.style.setProperty('--prototype-comments-min-height', '32px');
// Maximum width (default 400px)
overlay?.style.setProperty('--prototype-comments-max-width', '520px');- Local demo:
npm run dev, then open the printed URL - Live demo (GitHub Pages): enabled via GitHub Actions
- Push to
mainbuildsdist-demo/and deploys to Pages - Set your repo Pages source to GitHub Actions
- The site will be available at
https://<user>.github.io/<repo>/
- Push to
- Shift+C: toggle overlay visibility (handled by the library)
- C: toggle creation mode if you implement it (the drop-in controls wire this up for you)
Example creation-mode toggle without the controls:
let createMode = false;
const renderCursor = () => {
const cursor = createMode ? 'crosshair' : '';
document.body.style.cursor = cursor;
(document.documentElement as HTMLElement).style.cursor = cursor;
const overlay = document.querySelector('[data-prototype-comments-overlay]') as HTMLDivElement | null;
if (overlay) overlay.style.cursor = cursor;
};
window.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'c' && !e.shiftKey) {
createMode = !createMode;
renderCursor();
}
});
document.addEventListener('click', (e) => {
if (!createMode) return;
const x = (e as MouseEvent).clientX;
const y = (e as MouseEvent).clientY;
commentManager.beginCommentAt(x, y);
});- Overlay uses a high z-index so it sits on top of your UI. You can query the overlay via
[data-prototype-comments-overlay]and adjust styles at runtime if needed. - Comment bubble width can be tuned via CSS variables on the overlay element:
const overlay = document.querySelector('[data-prototype-comments-overlay]') as HTMLDivElement | null;
overlay?.style.setProperty('--prototype-comments-min-width', '520px');
overlay?.style.setProperty('--prototype-comments-min-height', '32px');
overlay?.style.setProperty('--prototype-comments-max-width', '520px');const { enableComments, exportComments, commentManager } = require('prototype-comments');
enableComments({ storage: 'localStorage', exportFormat: 'json' });
commentManager.addComment({ text: 'Hi', x: 50, y: 80 });
console.log(exportComments());Only call enableComments() on the client. For React/Next.js:
import { useEffect } from 'react';
import { enableComments, mountCommentControls } from 'prototype-comments';
export default function Page() {
useEffect(() => {
enableComments({ storage: 'localStorage', exportFormat: 'json' });
const unmount = mountCommentControls();
return () => unmount();
}, []);
return <main>…</main>;
}UMD bundle is exposed as window.PrototypeComments.
<script src="https://unpkg.com/prototype-comments/dist/index.umd.js"></script>
<script>
const { enableComments, commentManager } = window.PrototypeComments;
enableComments({ storage: 'localStorage', exportFormat: 'json' });
document.addEventListener('click', (e) => {
const x = e.clientX; const y = e.clientY;
commentManager.beginCommentAt(x, y);
});
</script>This package exports a React component PrototypeCommentsControls (optional). Mount it anywhere in your tree after calling enableComments() on the client.
import { useEffect } from 'react';
import { enableComments, PrototypeCommentsControls } from 'prototype-comments';
export default function Page() {
useEffect(() => {
enableComments({ storage: 'localStorage', exportFormat: 'json' });
}, []);
return (
<>
<main>…</main>
<PrototypeCommentsControls />
</>
);
}npm run devto start the demo site with hot reloadnpm run buildto build the library (ESM + CJS) and demonpm run testto run unit tests with Vitestnpm run lintto lint with ESLintnpm run formatto format with Prettier
- Fork and clone the repo
- Create a feature branch
- Run
npm i - Start the dev server with
npm run dev - Add tests for changes and ensure
npm run testpasses - Open a PR with a clear description
MIT