Skip to content

Getting Started

Jace edited this page Jun 6, 2026 · 1 revision

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.

Installation

Install the package for your framework of choice. Each framework package lists @highlighters/core as a dependency, so you do not install core separately.

Vanilla JS / Core only

npm install @highlighters/core
# or
pnpm add @highlighters/core
# or
yarn add @highlighters/core

React

npm install @highlighters/react
# or
pnpm add @highlighters/react
# or
yarn add @highlighters/react

Requires React 18 or later.

Vue

npm install @highlighters/vue
# or
pnpm add @highlighters/vue
# or
yarn add @highlighters/vue

Requires Vue 3.3 or later.

Svelte

npm install @highlighters/svelte
# or
pnpm add @highlighters/svelte
# or
yarn add @highlighters/svelte

Requires 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.


The 5-minute path (vanilla)

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 state

highlight() 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 });

The third argument: host

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);

What you get by default

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, the mild palette's default yellow swatch.
  • Opacity 0.55, composited with mix-blend-mode: multiply so overlaps darken and text stays readable.
  • Tip a chisel nib at 35 degrees, with a 2px overshoot past each text edge.
  • Organic edges wavy and lightly rough, with round caps. Set edge.waviness and edge.roughness to 0 for clean geometric edges.
  • Draw-on animation a 420ms left-to-right swipe with a 90ms per-line stagger, automatically suppressed under prefers-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: color is a CSS color string (hex, rgb(), hsl(), named, currentColor, var(...)) or a palette reference object { palette, swatch }. There is no preset option 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.


React

The React package exports the Highlight component and the useHighlight hook.

Component

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>

Hook

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.


Vue

The Vue package exports the Highlight component and the useHighlight composable.

Component

<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.

Composable

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.


Svelte

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>

Highlighting a whole page or the live selection

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.


TypeScript

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

Next steps

Clone this wiki locally