# Marker highlight

> A highlighter swipes behind the words.

- Type: Component (draw)
- Page: https://textlab.dev/components/marker-highlight
- Markdown: https://textlab.dev/components/marker-highlight.md
- Registry item: https://textlab.dev/r/marker-highlight.json
- Source file: `registry/textlab/marker-highlight/marker-highlight.tsx`
- Dependencies: none
- Variants: marker, underline, box
- Keywords: highlight, marker, highlighter, mark, emphasis, text highlight

## When to use

- Key phrases inside paragraphs and testimonials.
- Phrases that wrap across lines.

## When not to use

- Whole paragraphs.
- As the only way to convey meaning; color alone is not enough.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/marker-highlight
pnpm dlx shadcn@latest add @textlab/marker-highlight
```

The `@textlab` name needs `"registries": { "@textlab": "https://textlab.dev/r/{name}.json" }` in `components.json`. Without it, use the full URL:

```bash
npx shadcn@latest add https://textlab.dev/r/marker-highlight.json
```

Import the tokens once in your root layout: `import "@/components/textlab/tokens.css";`

### Manual

1. Install the dependencies: `npm install motion@12`
2. Copy `text-fx` (https://textlab.dev/r/text-fx.json) and this component into `components/textlab/`.

## Usage

```tsx
import { MarkerHighlight } from "@/components/textlab/marker-highlight/marker-highlight";

export function Example() {
  return <p>Textlab is <MarkerHighlight>free and open source</MarkerHighlight>.</p>;
}
```

## API reference

### MarkerHighlight

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` | – | The words to highlight. |
| `variant` | `"marker" \| "underline" \| "box"` | `"marker"` | Full highlighter stroke, thick low band, or soft block. |
| `color` | `string` | `var(--tl-fx-highlight)` | Highlight color. |
| `as` | `ElementType` | `"mark"` | Rendered element. |
| `trigger` | `"mount" \| "inView" \| "hover" \| "manual"` | `"inView"` | What starts the animation. manual follows `active`. |
| `active` | `boolean` | `false` | Drives trigger="manual". |
| `once` | `boolean` | `true` | In view: play once instead of on every entry. |
| `delay` | `number` | `0` | Seconds before the first part moves. |
| `duration` | `number` | `0.7` | Seconds each part takes. |
| `ease` | `EaseName \| [number, number, number, number]` | `"standard"` | Easing token or cubic bezier. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |

## Accessibility

- Renders <mark>, which assistive tech can announce as highlighted.
- Text color is inherited and stays on top of the band.

## Motion

- The band grows left to right with the standard ease, like a real marker stroke.

## Reduced motion

The highlight is already there.

## Responsive behavior

- Each wrapped line gets its own highlight thanks to box-decoration-break: clone.

## Performance

- One CSS transition on background-size; no JavaScript animation.

## Notes for AI

- Use only the props documented above. Do not invent props.
- Keep the screen-reader text and the reduced-motion branch when editing the source.
- Pick a variant from: marker, underline, box.

## Related

- [Underline draw](https://textlab.dev/components/underline-draw.md): A hand-drawn underline sketches in.
- [Circle annotate](https://textlab.dev/components/circle-annotate.md): A pen circles the word that matters.
- [Read along](https://textlab.dev/components/read-along.md): A highlight follows the reading, word by word.

## Also in draw

- [Outline draw](https://textlab.dev/components/outline-draw.md): The outline draws itself, then fills.
- [Signature](https://textlab.dev/components/signature.md): A handwritten signature writes itself.
- [Strike-through](https://textlab.dev/components/strike-through.md): Cross out a word and write the new one.

## Source

### components/textlab/marker-highlight/marker-highlight.tsx

```tsx
"use client";

import type { CSSProperties, ElementType, ReactNode } from "react";
import { cssEase, type EaseInput } from "../lib/motion-tokens";
import { useReduced, type ReducedMotion } from "../lib/use-reduced";
import { useTrigger, type Trigger } from "../lib/use-trigger";

export type MarkerHighlightVariant = "marker" | "underline" | "box";

export interface MarkerHighlightProps {
  children: ReactNode;
  /** marker is a full highlighter stroke, underline a thick low band, box a soft rounded block. */
  variant?: MarkerHighlightVariant;
  /** Highlight color. Defaults to the highlight token, which adapts to dark mode. */
  color?: string;
  as?: ElementType;
  trigger?: Exclude<Trigger, "always">;
  active?: boolean;
  once?: boolean;
  delay?: number;
  duration?: number;
  ease?: EaseInput;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
}

const BANDS: Record<MarkerHighlightVariant, { height: string; position: string; radius: string }> = {
  marker: { height: "78%", position: "0 70%", radius: "0.15em 0.35em 0.2em 0.3em" },
  underline: { height: "32%", position: "0 92%", radius: "0.1em" },
  box: { height: "100%", position: "0 50%", radius: "0.25em" },
};

/**
 * A highlighter swipes behind the words. Works across line breaks: every line fragment gets its
 * own stroke, because the background is cloned per line box. Renders a real <mark> by default.
 */
export function MarkerHighlight({
  children,
  variant = "marker",
  color = "var(--tl-fx-highlight, oklch(0.92 0.15 100 / 0.75))",
  as: Tag = "mark",
  trigger = "inView",
  active,
  once = true,
  delay = 0,
  duration = 0.7,
  ease = "standard",
  reducedMotion = "user",
  className,
  style,
}: MarkerHighlightProps) {
  const { ref, on, bind } = useTrigger<HTMLElement>({ trigger, active, once });
  const reduced = useReduced(reducedMotion);
  const band = BANDS[variant];
  const shown = on || reduced;

  return (
    <Tag
      ref={ref}
      className={className}
      style={{
        color: "inherit",
        backgroundColor: "transparent",
        backgroundImage: `linear-gradient(${color}, ${color})`,
        backgroundRepeat: "no-repeat",
        backgroundPosition: band.position,
        backgroundSize: `${shown ? 100 : 0}% ${band.height}`,
        transition: reduced ? "none" : `background-size ${shown ? duration : 0.2}s ${cssEase(ease)} ${shown ? delay : 0}s`,
        borderRadius: band.radius,
        padding: "0 0.12em",
        margin: "0 -0.12em",
        boxDecorationBreak: "clone",
        WebkitBoxDecorationBreak: "clone",
        ...style,
      }}
      {...bind}
    >
      {children}
    </Tag>
  );
}

export default MarkerHighlight;
```

Full library index: https://textlab.dev/llms.txt
