motion-marker

A highlighter stroke that sweeps behind a phrase when it scrolls into view, the way you would mark a printed page. The straight stroke follows text that wraps across lines, and shape="wave" draws a wavy one. The text itself never changes.

Preview

Ship motion that feels physical, not mechanical.

Import

import 'motion-components/motion-marker'   // just this one
import 'motion-components'               // or the whole library

Usage

<p>Ship motion that feels <motion-marker>physical, not mechanical</motion-marker>.</p>

Color

Set --mc-marker-color. A transparent color keeps the text readable.

Every spring is interruptible.

<motion-marker style="--mc-marker-color: rgb(96 165 250 / 0.35)">interruptible</motion-marker>

Wave

Set shape="wave" for a wavy stroke. It keeps its text on one line, so use it on a word or a short phrase.

Motion that feels hand-drawn.

<motion-marker shape="wave">hand-drawn</motion-marker>

Text that wraps

On a phrase that wraps, every line gets its own stroke, and all of them draw together.

Drop in a tag and the spring comes free with every single component.

In a sentence

Mix the marks and draw them one after another with delay.

Replace the manual checklist with one HTML tag, tested and reversible.

<p>
  Replace the <motion-strike>manual checklist</motion-strike>
  with <motion-marker delay="0.5">one HTML tag</motion-marker>,
  <motion-underline delay="1">tested</motion-underline>
  and <motion-ring delay="1.5">reversible</motion-ring>.
</p>

Loop

Add loop to draw, hold, undraw and draw again. hold sets how long the highlight stays on the text, gap how long it stays away. Add pause-on-hover to freeze it while the reader points at it. The highlight erases from its start, so it leaves at its end.

Makes the code feel alive.

<motion-marker loop hold="2" pause-on-hover>the code</motion-marker>

Properties

Attribute Property Type Default Description
trigger trigger string 'view' What draws the mark: 'view' when it scrolls into view, 'hover' on pointer enter (it undraws on leave), or 'mount' when it connects
duration duration number 0.6 Spring duration of the drawing, in seconds
delay delay number 0 Seconds to wait before drawing. Use it to draw several marks in one sentence one after another
bounce bounce number 0 Spring bounciness. 0 keeps the stroke from overshooting the text
loop loop boolean false Draw, hold, undraw and draw again on repeat. With trigger="view" the cycle runs while the mark is on screen
hold hold number 1.6 With loop, seconds the mark stays drawn before it undraws
gap gap number 0.5 With loop, seconds the mark stays undrawn before it draws again
pause-on-hover pauseOnHover boolean false Pause the loop while the pointer is over the mark, resuming on leave
once once boolean true With trigger="view", draw only the first time. Set once="false" to undraw when it leaves and draw again on every entry. Ignored with loop
threshold threshold number 0.6 Share of the element, from 0 to 1, that must be visible before it draws
shape shape string 'line' 'line', a straight stroke that follows wrapping text, or 'wave', a wavy stroke that keeps the text on one line

CSS custom properties

Property Type Default Description
--mc-marker-color color rgb(250 204 21 / 0.4) Color of the highlighter stroke

JavaScript API

The mark has the shared playback API: play() draws it, cancel() removes it, finish() shows it fully drawn and replay() draws it again from the start. It fires motion-start, motion-finish and motion-cancel. With trigger="hover", each draw and undraw is a playback run too, so it fires motion-start and motion-finish. See the JavaScript API.

sweep() runs a fully drawn mark across once: it erases from its start, so it leaves at its end, then draws in again. It is a playback run, so it fires motion-start and motion-finish, and pause(), finish() and cancel() act on it. It does nothing under reduced motion or while the mark is not fully drawn.

Accessibility

The mark is decoration: the text stays real, selectable and read by screen readers exactly as written. If the highlight carries meaning, wrap the component in <mark>. Respects prefers-reduced-motion: the mark appears fully drawn at once, with no animation, and loop never starts.