Highlighter

Marks drawn by hand over a few words: a marker band behind them, a line under or through them, a box or a loop around them. Each mark draws in the first time the text comes into view, and keeps its shape when the text wraps again.

Examples

In a paragraph

The marker band sits behind the text, so the words stay sharp. The underline waits until the band is done.

The weekly digest collects every decision made in review, so the people who missed the meeting can catch up in five minutes.

Every mark

Five marks, each in a tone that keeps its meaning. Lines and loops go over twice, as a pen would.

  • highlightDue on Friday
  • underlineRead before the call
  • boxTwo seats left
  • circleApproved
  • strikeDraft pricing

Shown on demand

With show, the marks follow your state: they draw when it turns true and fade out faster when it turns false.

The launch moves from Thursday to Friday at 10:00, after the pricing review.

Across lines

A phrase that wraps gets one mark per line, drawn one line after the other, each for its share of the duration.

Before you merge, check that every screen still reads well in the dark theme and at the smallest phone width.

Installation

npx shadcn@latest add https://ui.ndekocode.dev/r/motion-highlighter.json

Component API

Highlighter

PropTypeDefaultDescription
childrenReact.ReactNodeThe words to mark. Keep them inline: text, links, strong.
action'highlight' | 'underline' | 'box' | 'circle' | 'strike''highlight'The mark to draw.
tone'primary' | 'success' | 'warning' | 'info' | 'destructive''warning' for highlight, 'primary' for othersThe colour token of the mark.
strokeWidthnumber2Width of lines and loops, in pixels. The marker band follows the text height.
paddingnumber2, 5 for box, 8 for circleRoom between the text and the mark, in pixels.
passes1 | 21 for highlight, 2 for othersHow many times the pen goes over the mark.
durationnumber0.8Seconds to draw the whole mark, shared between the lines it covers.
delaynumber0Seconds before drawing starts.
showbooleanControls the mark. When it is not set, the mark draws once the text is in view.
classNamestringClasses for the wrapping span.

Accessibility and motion

  • The marks are a decorative SVG hidden from assistive technology. When a mark carries meaning, such as a price that no longer applies, say it in the text too, or wrap it in del and ins.
  • Marker bands use the fill tokens at 35% opacity behind the text. Lines use the status text pairs (text-warning-foreground, text-destructive-ink...), measured for both themes, and the primary colour.
  • The shapes come from a seeded generator, so a mark keeps its shape when the window is resized and only its position follows the new line breaks.
  • With reduced motion, the marks fade in at full length instead of being drawn.

Credits

Inspired by the highlighter of Magic UI. The hand-drawn shapes are our own SVG paths, with no annotation library.