penciled (penciled.org, also called penciled.js) is a 15 kB open-source JavaScript library for persistent text highlights and notes on any web page, with a browser extension built on it. Highlights are painted with the CSS Custom Highlight API, so your DOM is never touched. Think Sonner, for text annotations.
Use it to add Medium-style highlights and notes to a blog, docs site or web app, with no backend required.
penciled.org is an open-source text highlighting library and browser extension. It is not affiliated with PENCIL, the nonprofit at pencil.org, or with Penciled, the healthcare company at penciled.com.
Browser support: Chrome/Edge 105+, Safari 17.2+, Firefox 140+; no fallback on older browsers, annotate() only warns. Details
Documentation · Playground · npm · Compare · Browser extension
npm install penciledimport { annotate } from 'penciled';
import 'penciled/styles.css';
annotate('#article');Readers can now select text to highlight it, add a note, or click a highlight to change it. Annotations are saved to localStorage and come back on reload.
- Beautiful by default. A floating toolbar and note editor that appear only when needed, in light and dark themes.
- Yours to style. Theme the UI with CSS custom properties, or restyle anything: default selectors have (almost) zero specificity, so your CSS wins. Need a different UI altogether? Go headless and build your own. See Theming.
- Framework-agnostic. Plain DOM, with an optional React adapter. Highlights are painted with the CSS Custom Highlight API, so your DOM is never modified.
- Reliable anchoring. Highlights are re-anchored when the content changes. When penciled can't be sure where a highlight belongs, it leaves it out instead of highlighting the wrong text.
- Persistent. Uses
localStorageout of the box. Plug in your own storage adapter to save to a backend. - Accessible. Keyboard shortcuts for the toolbar and for moving between annotations, with screen-reader announcements.
- Small. About 15 kB min+gzip with the UI, 6 kB headless. One dependency (
@floating-ui/dom).
Not building a site? penciled for the browser lets you highlight and take notes on any website, in Chrome, Edge and other Chromium browsers 123+ and Firefox 140+. Install it for Chrome, Firefox or Edge. It's built on this library and lives in apps/extension.
annotate('#article', {
documentId: 'post-42',
storage: {
load: (documentId) => fetch(`/api/annotations/${documentId}`).then((r) => r.json()),
save: (documentId, annotations) =>
fetch(`/api/annotations/${documentId}`, {
method: 'PUT',
body: JSON.stringify(annotations),
}).then(() => {}),
},
});Adapters can also implement create, update and delete to save one annotation at a time. See Custom storage.
import { Annotatable } from 'penciled/react';
import 'penciled/styles.css';
export function Article({ children }) {
return <Annotatable options={{ documentId: 'post-42' }}>{children}</Annotatable>;
}There is also a useAnnotations(ref, options) hook. See React.
penciled/headless is the engine without the UI. It handles anchoring, painting, persistence and events, and you build the interface with whatever components and styles you already use.
import { annotate } from 'penciled/headless';
const annotator = annotate('#article');
annotator.on('select', (context) => {
// Show your own UI for the selected text or annotation.
});Chrome and Edge 105+, Safari 17.2+ and Firefox 140+, on desktop and mobile. With the default styles, the minimums are Chrome and Edge 123+ and Safari 17.5+. Firefox 140 to 148 support ::highlight() only in part, which affects only the optional note underline (noteIndicator: 'underline'): it shows from Firefox 146. In older browsers annotate() logs a warning and does nothing. Importing is safe during server-side rendering. See Browser support.
penciled (penciled.org, also called penciled.js) is a 15 kB open-source JavaScript library that lets readers highlight text and write notes on any web page. Call annotate('#article') and readers get a floating toolbar and a note editor. Annotations are saved to localStorage, or to your own backend through a storage adapter. It's MIT licensed and published on npm as penciled.
No. Highlights are painted by the browser with the CSS Custom Highlight API, so your text is never wrapped in <mark> or <span> elements, and frameworks can re-render the page as often as they like. The only elements penciled adds are its own toolbar, note editor and screen-reader live region, outside your content.
Yes. penciled/react has a useAnnotations() hook and an <Annotatable> component, and every entry point is safe to import during server-side rendering, in Next.js, Astro, Remix and others. Only annotate() needs a browser. With other frameworks, call annotate() on a mounted element and destroy() when it unmounts.
Yes. Pass a storage adapter with async load() and save() methods, and optionally create(), update() and delete() to save one annotation at a time. Annotations are plain JSON, and toW3C() converts them to W3C Web Annotations.
web-highlighter wraps each highlight in <span> elements and leaves the UI and storage to you. Recogito's Text Annotator is a larger framework built around the W3C Web Annotation model, with multi-user features, for research and collaborative annotation. penciled never changes your DOM, ships a toolbar, note editor and persistence, and is about 15 kB with the UI. In exchange it needs a browser with the CSS Custom Highlight API, while both of the others also work in older browsers. The comparison page at https://www.penciled.org/compare/ covers them, three more libraries and Hypothesis.
Yes. penciled for the browser lets readers highlight text and take notes on any website, in Chrome, Edge and other Chromium browsers 123+ and Firefox 140+. It's free, open source and built on this library. Highlights stay in the browser, with no account, and nothing is sent anywhere. See https://www.penciled.org/extension/
Not automatically. The extension saves highlights per page address, ignoring the #fragment, a leading www., a trailing slash and common tracking parameters such as utm_source. If a page moves to a new address, or the same article is reachable at two different addresses, highlights made at one address don't show at the other. In the JavaScript library, annotations are stored under the page's path by default; set documentId to keep them when URLs change.
No. Highlights stay in the browser you made them in, with no account and no cloud. Only your settings sync, through your browser account. To move highlights to another browser, export a backup from the notes page and import it there.
No. The extension and the library highlight text on web pages, not PDFs. For PDFs, a tool like Hypothesis is a better fit; see https://www.penciled.org/alternatives/
Yes, as a file. Export as Markdown on the extension's notes page downloads a .md file with each highlight quoted with its note, which you can open in Obsidian, Logseq or any notes app. There's no integration or sync with them: each export is a one-off copy.
No. penciled.org is an open-source text highlighting library and browser extension. It is not affiliated with PENCIL, the nonprofit at pencil.org, or with Penciled, the healthcare company at penciled.com.
| Path | What |
|---|---|
packages/penciled |
The library: penciled, penciled/headless, penciled/react and penciled/styles.css |
apps/docs |
The documentation site and playground (Astro + Starlight) |
pnpm install
pnpm test
pnpm docs:devRead CONTRIBUTING.md before opening a pull request.
MIT © Marcel Losso
