-
-
Notifications
You must be signed in to change notification settings - Fork 3
Getting Started
This guide covers installation and the 5-minute path for @highlighters: realistic highlighter-pen marks over any web text. We start with vanilla highlight(), then cover the React, Vue, and Svelte quick starts. All examples use the real API.
@highlighters draws a wet-ink mark over text without touching the text itself: organic frayed edges, lengthwise streaking, and true multiplicative ink optics (mix-blend-mode: multiply, so overlapping marks darken and dark text stays legible). It handles wrapped lines as one continuous swipe, repositions on reflow and web-font load, and leaves selection and find-in-page working.
Install the package for your framework of choice. Each framework package lists @highlighters/core as a dependency, so you do not install core separately.
npm install @highlighters/core
# or
pnpm add @highlighters/core
# or
yarn add @highlighters/corenpm install @highlighters/react
# or
pnpm add @highlighters/react
# or
yarn add @highlighters/reactRequires React 18 or later.
npm install @highlighters/vue
# or
pnpm add @highlighters/vue
# or
yarn add @highlighters/vueRequires Vue 3.3 or later.
npm install @highlighters/svelte
# or
pnpm add @highlighters/svelte
# or
yarn add @highlighters/svelteRequires Svelte 3 or later.
The packages are zero-dependency, ship ESM + CJS, and bundle their own TypeScript declarations. There are no @types packages to install.
Import highlight, point it at something, and you have a mark. The function returns a MarkHandle that controls the mark's lifecycle.
import { highlight } from "@highlighters/core";
// Highlight an element by selector with the library defaults.
const handle = highlight("#intro");
// The handle controls the mark.
handle.hide();
handle.show();
handle.remove(); // restores the DOM to its pre-highlight statehighlight() takes any Target: an Element, a CSS selector string, a Range, a Selection, a text query, or a page target.
import { highlight } from "@highlighters/core";
// An element node.
highlight(document.querySelector(".lede")!);
// Every match of a string or RegExp within a root.
highlight({ text: "deterministic", root: document.body });
highlight({ text: /\bink\b/gi });Tune the mark with a partial HighlightOptions as the second argument. Options use real-highlighter vocabulary: tip, ink flow, edge waviness, and so on.
import { highlight } from "@highlighters/core";
highlight(".key-point", {
markType: "highlight",
color: "#ffd54a",
opacity: 0.6,
tip: { type: "chisel", angle: 35 },
ink: { flow: 0.5, streakiness: 0.3 },
});update() re-resolves options without re-seeding the stable geometry, so a mark that is already down stays put as you retune it.
const handle = highlight("#intro");
handle.update({ color: "#9bff5a", opacity: 0.7 });By default the overlay mounts into the document body. If your text lives inside a transformed, scrolling, or stacked container, pass a positioned element as the third argument so the overlay tracks it. A static host is promoted to position: relative automatically.
const panel = document.querySelector(".panel")!;
highlight(".panel .term", { snap: "word" }, panel);Calling highlight(target) with no options resolves this baseline:
-
Mark type
highlight(a tall band over the text). Other kinds:underline,overline,strike-through. -
Color
#f5e6a8, themildpalette's default yellow swatch. -
Opacity
0.55, composited withmix-blend-mode: multiplyso overlaps darken and text stays readable. -
Tip a
chiselnib at35degrees, with a2pxovershoot past each text edge. -
Organic edges wavy and lightly rough, with
roundcaps. Setedge.wavinessandedge.roughnessto0for clean geometric edges. -
Draw-on animation a
420msleft-to-right swipe with a90msper-line stagger, automatically suppressed underprefers-reduced-motion: reduce. - Renderer auto-selected. The highest-fidelity SVG tier is used where supported, degrading to a lightweight CSS tier under reduced motion, reduced data, or when more than 50 marks are visible. See How-It-Works and Performance.
-
Snap derived from the target: element and selector targets default to
"line"; ranges, selections, and text queries default to"word". See Snapping-and-Overshoot. - Determinism every random value derives from a seed, never the wall clock. Identical inputs produce byte-identical marks across scroll, reflow, reload, and server rendering.
The text is never altered, overlays are non-interactive, and remove() restores the original DOM. See Accessibility-and-Reflow.
Note on color:
coloris a CSS color string (hex,rgb(),hsl(), named,currentColor,var(...)) or a palette reference object{ palette, swatch }. There is nopresetoption and no bare named-swatch shorthand: a string like"pink"is passed straight through as a CSS color, not a palette lookup. To pull from a curated family, use{ color: { palette: "fluorescent", swatch: "pink" } }or{ palette: "mild" }for that family's default swatch. See Color-and-Palettes.
The React package exports the Highlight component and the useHighlight hook.
Highlight renders an element (default <span>) whose text content is highlighted. Pass options through the options prop; any other HTML attributes pass through to the rendered element.
import { Highlight } from "@highlighters/react";
function Article() {
return (
<p>
The part that matters most is{" "}
<Highlight options={{ color: { palette: "fluorescent", swatch: "yellow" } }}>
this exact sentence
</Highlight>
.
</p>
);
}Use the as prop to change the rendered element. It is polymorphic, so the available HTML attributes follow the chosen tag.
<Highlight as="strong" options={{ markType: "underline" }} className="lede">
load-bearing phrase
</Highlight>useHighlight applies a mark to a ref, a DOM node, or a core Target, and returns a ref to the live MarkHandle. Option changes are pushed via handle.update() without re-seeding geometry.
import { useRef } from "react";
import { useHighlight } from "@highlighters/react";
function Lede() {
const ref = useRef<HTMLParagraphElement>(null);
useHighlight(ref, { color: "#aacfe0", opacity: 0.6 });
return <p ref={ref}>This whole paragraph gets a mark.</p>;
}The hook re-checks the target each render, so a RefObject that populates after mount is picked up. An optional third argument is the host element, matching the vanilla signature.
The Vue package exports the Highlight component and the useHighlight composable.
<script setup>
import { Highlight } from "@highlighters/vue";
</script>
<template>
<p>
The part that matters most is
<Highlight :options="{ color: { palette: 'fluorescent', swatch: 'yellow' } }">
this exact sentence
</Highlight>
.
</p>
</template>The as prop sets the rendered tag (default "span"). Non-prop attributes are spread onto the rendered element.
<template>
<Highlight as="em" :options="{ markType: 'strike-through' }" class="note">
struck out
</Highlight>
</template>The component exposes { el, handle }, where handle is a getter returning the live MarkHandle or null.
useHighlight applies a mark to a template ref (or a core Target) and returns a getter for the live MarkHandle. Pass options as a ref to have changes pushed reactively via handle.update().
<script setup>
import { ref } from "vue";
import { useHighlight } from "@highlighters/vue";
const el = ref(null);
useHighlight(el, { color: "#bfe0b2", opacity: 0.6 });
</script>
<template>
<p ref="el">This whole paragraph gets a mark.</p>
</template>The composable sets up on onMounted and cleans up on onBeforeUnmount. If the target appears after setup, the next sync recreates the mark.
The Svelte package exports the highlight action. There is no component wrapper: apply the action directly to an element with use:.
<script>
import { highlight } from "@highlighters/svelte";
</script>
<p use:highlight>This whole paragraph gets a mark.</p>Pass options as the action parameter. Changing the parameter pushes new options through the live handle, preserving geometry; the action's destroy removes the mark and restores the DOM.
<script>
import { highlight } from "@highlighters/svelte";
const options = {
markType: "underline",
color: { palette: "calm", swatch: "sky" },
edge: { waviness: 0 },
};
</script>
<p use:highlight={options}>A clean-edged underline.</p>Two more entry points cover declarative and interactive cases:
import { highlightAll, highlightSelection } from "@highlighters/core";
// Mark every [data-highlight] element plus a page scan, kept in sync by a MutationObserver.
const page = highlightAll();
// Follow the user's live selection in real time (defers to native UI on touch).
const live = highlightSelection({ fadeOnClear: true });highlightAll() defaults snap to "line" and marks any element carrying the data-highlight attribute. highlightSelection() defaults snap to "word" and powers speed-aware ink during a fine-pointer drag. See Page-Highlighting and Selection-Highlighting.
All packages ship their own declarations. The framework packages re-export the core types for convenience, so import whichever package you already depend on:
import type {
HighlightOptions,
MarkHandle,
Target,
PaletteName,
MarkType,
} from "@highlighters/react"; // or /vue, /svelte, /core-
Which-API-Should-I-Use: choosing between
highlight,highlightAll,highlightSelection, and the bindings - Mark-Types-and-Shapes: highlight, underline, overline, strike-through
- Color-and-Palettes: CSS colors, palette families, and swatches
- Ink-and-Optics: flow, viscosity, feathering, streaking, dry-out, and blend modes
- Animation: draw-on swipe, stagger, and scroll triggers
- Options-Reference: every option with its default and range
- API-Reference: full function and type reference
Getting Started
The Mark
- Mark-Types-and-Shapes
- Tips-and-Edges
- Ink-and-Optics
- Color-and-Palettes
- Snapping-and-Overshoot
- Animation
Targeting
Reference
Production