Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Medium-style text highlighting with private notes — engine, selection toolbar, action components, and persistence layer.
Vanilla Breeze ships a Medium-style highlighting system: select text, pick a color, optionally add a private note. Highlights persist in localStorage per page. Notes live in a panel that opens when you click the margin annotation, and the storage layer is a single private object behind a controller — easy to export, import, or wire to a sync backend later.
The system is layered. Each layer has one job and you opt into the layers you want.
| Layer | What it does | How you use it |
|---|---|---|
Enginedata-highlights attributesrc/utils/highlights-init.js |
Range serialization, persistence, CSS Custom Highlight API rendering (with <mark> fallback), margin annotations, public mutation API. Emits highlights:added, highlights:removed, highlights:clicked, highlights:request-note, highlights:selected. |
<article data-highlights> on any container. Without other layers it renders read-only stored highlights and exposes the controller API. |
Selection toolbar<selection-menu> |
Floating toolbar that appears when a user selects text in a target element. Composes action children. Detects selection, positions itself, dismisses on outside click / Escape. | <selection-menu for="article-id">…</selection-menu> with action children inside. |
Highlight action<highlight-wc> |
Inside <selection-menu>: icon button + color palette dropdown that commits a highlight via the engine. Standalone: HTML-first sugar that turns a target into a data-highlights container. |
Inside selection-menu: <highlight-wc colors="red,green"></highlight-wc>. Standalone: <highlight-wc for="article-id"></highlight-wc>. |
Note action + panel host<note-wc> |
Inside <selection-menu>: highlight + open note panel in one click. Anywhere on the page: hosts the note panel UI for any highlight that requests one (margin-annotation clicks, programmatic openFor()). |
<note-wc></note-wc> as a selection-menu child, OR placed once on the page so margin annotations have a panel to open. |
Just the engine, no UI. Renders highlights persisted under the page's pathname; new selections do nothing.
<article data-highlights data-highlights-readonly> <p>Existing highlights from localStorage paint here.</p></article>
Engine + selection-menu + highlight-wc + note-wc. The 4-line vanilla recipe.
<article id="post" data-highlights> <p>Select text to highlight. Click a margin annotation to add a note.</p></article> <selection-menu for="post"> <highlight-wc></highlight-wc> <note-wc></note-wc></selection-menu>
Define --highlight-{name} CSS tokens, then list the names on both data-highlights-colors (engine) and colors (highlight-wc swatches).
<style> .brand-article { --highlight-brand: #fce7f3; --highlight-accent: #ddd6fe; }</style><article id="post" class="brand-article" data-highlights data-highlights-colors="brand,accent,yellow"> <p>Custom palette per article.</p></article><selection-menu for="post"> <highlight-wc colors="brand,accent,yellow"></highlight-wc> <note-wc></note-wc></selection-menu>
Drop a note-wc with for= into the page-tools rail; clicking it opens the note panel for the first highlight on that article.
<page-tools> <print-page></print-page> <share-wc></share-wc> <note-wc for="post"></note-wc></page-tools>
| Event | Detail | When |
|---|---|---|
highlights:added | { id, color, text } | A highlight is created via the controller |
highlights:removed | { id } | A highlight is deleted |
highlights:clicked | { id, highlight } | The user clicks an existing highlight (Custom Highlight API hit-test, <mark> fallback, or margin annotation) |
highlights:request-note | { id, highlight, controller } | Margin-annotation click — the convention for opening a note. <note-wc> listens at document level and opens its panel. |
highlights:selected | { text, rect } | User finishes a selection inside a target. Ignored by <selection-menu> (it has its own listener); useful for power users without selection-menu. |
The HighlightController instance is returned from initHighlights(element). Each [data-highlights] element has exactly one controller (cached).
| Method / property | Purpose |
|---|---|
getHighlights() | Snapshot of all highlights (array of { id, color, text, startOffset, endOffset, note }) |
removeHighlight(id) | Delete one |
clearAll() | Wipe all highlights for this element |
exportHighlights() | JSON string for backup / sync |
importHighlights(json) | Replace highlights from JSON |
findHighlightRect(id) | First viewport rect of a highlight (used by note-wc to align its panel) |
colors | Active color names |
element | The owning DOM element |
Internal mutation methods _createFromSelection(color), _changeColor(id, color), and _updateNote(id, text) are for action components (highlight-wc, note-wc) and are not part of the documented public API.
Highlights are written to localStorage under the namespace vb-highlights with a key derived from data-highlights (or the page's pathname when the attribute has no value). Multiple articles on the same page can share a key by setting the same value.
<!-- Default: storage key = location.pathname --><article data-highlights>…</article> <!-- Explicit key: storage key = "blog-post-42" --><article data-highlights="blog-post-42">…</article> <!-- Two articles share one set of highlights --><article data-highlights="shared">…</article><article data-highlights="shared">…</article>