# Strike-through

> Cross out a word and write the new one.

- Type: Component (draw)
- Page: https://textlab.dev/components/strike-through
- Markdown: https://textlab.dev/components/strike-through.md
- Registry item: https://textlab.dev/r/strike-through.json
- Source file: `registry/textlab/strike-through/strike-through.tsx`
- Dependencies: motion
- Variants: replace, line, scribble
- Keywords: strikethrough, cross out, correction, hand drawn, edit

## When to use

- Witty headlines that correct themselves.
- Pricing before and after, changelog wording.

## When not to use

- Legal or factual corrections; use <del> and <ins> styled plainly.
- More than one per line.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/strike-through
pnpm dlx shadcn@latest add @textlab/strike-through
```

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/strike-through.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 { StrikeThrough } from "@/components/textlab/strike-through/strike-through";

export function Example() {
  return <p>Meetings that could have been an <StrikeThrough text="email" variant="replace" replacement="👍" /></p>;
}
```

## API reference

### StrikeThrough

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"replace" \| "line" \| "scribble"` | `"line"` | Cross out and write a correction, a single line, or a scratch-out. |
| `replacement` | `string` | – | The correction written above, for replace. |
| `color` | `string` | `var(--tl-accent)` | Pen color. |
| `thickness` | `number` | `3` | Stroke width in px. |
| `as` | `ElementType` | `"span"` | 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.5` | Seconds each part takes. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |

## Accessibility

- Screen readers hear the meaning: the replacement and what it replaces, or that the word is struck out.
- The pen line and correction are decorative.

## Motion

- The old word dims just after the pen passes, the correction arrives last.

## Reduced motion

Shown already struck, with the correction in place.

## Performance

- One path and one small text element.

## 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: replace, line, scribble.

## 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.
- [Text morph](https://textlab.dev/components/text-morph.md): One word turns into the next, letter by letter.

## 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.
- [Marker highlight](https://textlab.dev/components/marker-highlight.md): A highlighter swipes behind the words.

## Source

### components/textlab/strike-through/strike-through.tsx

```tsx
"use client";

import type { CSSProperties, ElementType } from "react";
import { AnimatePresence, motion } from "motion/react";
import { srOnly } from "../lib/a11y";
import { drawProps } from "../lib/draw";
import { hashString } from "../lib/math";
import { ease } from "../lib/motion-tokens";
import { roughStrike, roughStrikeScribble } from "../lib/rough";
import { useReduced, type ReducedMotion } from "../lib/use-reduced";
import { useTrigger, type Trigger } from "../lib/use-trigger";

export type StrikeThroughVariant = "line" | "scribble" | "replace";

export interface StrikeThroughProps {
  text: string;
  /** line crosses it out, scribble scratches it out, replace crosses it out and writes `replacement` above. */
  variant?: StrikeThroughVariant;
  /** The correction written above, for the replace variant. */
  replacement?: string;
  color?: string;
  thickness?: number;
  as?: ElementType;
  trigger?: Exclude<Trigger, "always">;
  active?: boolean;
  once?: boolean;
  delay?: number;
  duration?: number;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
}

/**
 * Cross out a word and, if you like, write the new one above it. The old word dims as the pen
 * passes, so the correction reads in the right order.
 */
export function StrikeThrough({ text, variant = "line", replacement, color = "var(--tl-accent, #3a9a5b)", thickness = 3, as: Tag = "span", trigger = "inView", active, once = true, delay = 0, duration = 0.5, reducedMotion = "user", className, style }: StrikeThroughProps) {
  const { ref, on, bind } = useTrigger<HTMLElement>({ trigger, active, once });
  const reduced = useReduced(reducedMotion);
  const struck = on || reduced;
  const d = (variant === "scribble" ? roughStrikeScribble : roughStrike)(hashString(text));
  const draw = drawProps(struck, { duration: variant === "scribble" ? duration * 1.6 : duration, delay });
  const showReplacement = variant === "replace" && replacement && struck;
  // An emoji correction pops in upright and larger; a word is handwritten small and slanted.
  const isEmoji = !!replacement && /^[\p{Extended_Pictographic}\p{Emoji_Component}\u200d\ufe0f\s]+$/u.test(replacement);
  const spoken = variant === "replace" && replacement ? `${replacement} (replaces ${text})` : `${text} (struck out)`;

  return (
    <Tag ref={ref} className={className} style={{ position: "relative", display: "inline-block", ...style }} {...bind}>
      <span style={srOnly}>{spoken}</span>
      <motion.span aria-hidden="true" initial={false} animate={{ opacity: struck ? 0.55 : 1 }} transition={{ duration: 0.3, delay: reduced ? 0 : delay + duration * 0.6 }}>
        {text}
      </motion.span>
      <svg aria-hidden="true" viewBox="0 0 100 20" preserveAspectRatio="none" style={{ position: "absolute", left: "-3%", width: "106%", top: "50%", marginTop: "-0.22em", height: "0.6em", overflow: "visible", pointerEvents: "none" }}>
        <motion.path d={d} fill="none" stroke={color} strokeWidth={thickness} strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" {...(reduced ? { initial: false, animate: { pathLength: 1, opacity: 1 } } : draw)} />
      </svg>
      <AnimatePresence>
        {showReplacement && (
          <motion.span
            aria-hidden="true"
            style={{ position: "absolute", left: "50%", bottom: isEmoji ? "70%" : "78%", whiteSpace: "nowrap", color, fontSize: isEmoji ? "0.85em" : "0.62em", fontStyle: isEmoji ? "normal" : "italic", lineHeight: 1 }}
            initial={reduced ? false : isEmoji ? { opacity: 0, transform: "translateX(-50%) translateY(0.4em) scale(0.4) rotate(-20deg)" } : { opacity: 0, transform: "translateX(-50%) translateY(0.3em) rotate(-3deg)", filter: "blur(4px)" }}
            animate={isEmoji ? { opacity: 1, transform: "translateX(-50%) translateY(0em) scale(1) rotate(-6deg)" } : { opacity: 1, transform: "translateX(-50%) translateY(0em) rotate(-3deg)", filter: "blur(0px)" }}
            transition={isEmoji ? { type: "spring", duration: 0.6, bounce: 0.3, delay: reduced ? 0 : delay + duration + 0.1 } : { duration: 0.5, delay: reduced ? 0 : delay + duration + 0.1, ease: [...ease.enter] }}
          >
            {replacement}
          </motion.span>
        )}
      </AnimatePresence>
    </Tag>
  );
}

export default StrikeThrough;
```

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