motion-ring

An ellipse or a box that draws itself around a word or a short phrase when it scrolls into view, like a pen circling something important. It keeps its text on one line.

Preview

Every change is reversible.

Import

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

Usage

<p>Every change is <motion-ring>reversible</motion-ring>.</p>

Box

Set shape="box" for a rectangle with slightly rounded corners.

Fully tested.

<motion-ring shape="box">tested</motion-ring>

Padding and bounce

padding sets the gap between the text and the stroke. A little bounce makes the ring playful.

Twice as fast.

<motion-ring padding="12" bounce="0.3" style="--mc-mark-thickness: 3px">as fast</motion-ring>

Loop

Add loop to draw, hold, undraw and draw again, like a pen that keeps circling the word. hold sets how long the ring stays around the text, gap how long it stays away. The ring erases from its start, so it leaves at its end.

Watch it again and again.

<motion-ring loop hold="2">again and again</motion-ring>

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 'ellipse' 'ellipse', or 'box' for a rectangle with slightly rounded corners
padding padding number 6 Gap in pixels between the text and the stroke

CSS custom properties

Property Type Default Description
--mc-mark-color color var(--mc-color-accent, #2563eb) Color of the stroke
--mc-mark-thickness length 2px Thickness of the 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. The drawing is hidden from screen readers. Respects prefers-reduced-motion: the mark appears fully drawn at once, with no animation, and loop never starts.