# Line reveal

> Lines lift in, re-measured on resize.

- Type: Component (reveal)
- Page: https://textlab.dev/components/line-reveal
- Markdown: https://textlab.dev/components/line-reveal.md
- Registry item: https://textlab.dev/r/line-reveal.json
- Source file: `registry/textlab/line-reveal/line-reveal.tsx`
- Dependencies: motion
- Variants: rise, mask, tilt
- Keywords: line, paragraph, resize, wrap, mask, tilt

## When to use

- Two to four line intros and pull quotes.
- Copy whose wrap changes across breakpoints.

## When not to use

- Single words; use word-reveal.
- Body text longer than a short paragraph.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/line-reveal
pnpm dlx shadcn@latest add @textlab/line-reveal
```

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/line-reveal.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 { LineReveal } from "@/components/textlab/line-reveal/line-reveal";

export function Example() {
  return <LineReveal as="p" text="Lines are measured from the real layout, so the reveal follows the wrap at every width." />;
}
```

## API reference

### LineReveal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"rise" \| "mask" \| "tilt"` | `"rise"` | rise lifts from a clip, mask uncovers top to bottom, tilt swings up on a hinge. |
| `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. |
| `stagger` | `number` | `0.1` | Seconds between parts. |
| `duration` | `number` | `0.8` | Seconds each part takes. |
| `ease` | `EaseName \| [number, number, number, number]` | `"enter"` | 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. |
| `onComplete` | `() => void` | – | Called when a one-shot animation finishes. |

## Accessibility

- The sentence is read once from a visually hidden copy.
- Line groups are aria-hidden presentation only.

## Motion

- Lines start 100 ms apart.
- Line breaks are measured after fonts load and again whenever the width changes.

## Reduced motion

Shows the paragraph as normal text with no grouping or movement.

## Responsive behavior

- Re-groups lines on resize, so narrow screens get more, shorter lines.

## Performance

- One measure per frame at most, triggered by ResizeObserver.
- Transform, opacity and clip-path only.

## 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: rise, mask, tilt.

## Related

- [Word reveal](https://textlab.dev/components/word-reveal.md): Words rise out of a clip.
- [In-view title](https://textlab.dev/components/in-view-title.md): Bring a section title in as it scrolls into view.
- [Split text](https://textlab.dev/components/split-text.md): Stagger anything by chars, words or lines.

## Also in reveal

- [Blur reveal](https://textlab.dev/components/blur-reveal.md): Words sharpen into focus.
- [Character reveal](https://textlab.dev/components/char-reveal.md): Letters arrive one at a time.
- [Wipe reveal](https://textlab.dev/components/wipe-reveal.md): A soft edge uncovers the line.
- [Tracking reveal](https://textlab.dev/components/tracking-reveal.md): Letter spacing breathes open as it appears.

## Source

### components/textlab/line-reveal/line-reveal.tsx

```tsx
"use client";

import { RevealText, type RevealBaseProps } from "../lib/reveal";

export type LineRevealVariant = "rise" | "mask" | "tilt";

export interface LineRevealProps extends RevealBaseProps {
  /** rise lifts each line out of a clip, mask uncovers it top to bottom, tilt swings it up on a hinge. */
  variant?: LineRevealVariant;
}

/**
 * Lines lift in one after another. Lines are measured from the real layout and re-measured
 * when the container resizes, so the reveal always follows how the text actually wraps.
 */
export function LineReveal({ variant = "rise", ...props }: LineRevealProps) {
  if (variant === "mask") {
    return <RevealText by="lines" hidden={{ clipPath: "inset(0% 0% 100% 0%)", y: "0.2em" }} shown={{ clipPath: "inset(0% 0% 0% 0%)" }} {...props} />;
  }
  if (variant === "tilt") {
    return (
      <RevealText
        by="lines"
        hidden={{ transformPerspective: 600, rotateX: -75, y: "0.4em", opacity: 0 }}
        partStyle={{ transformOrigin: "50% 0%" }}
        {...props}
      />
    );
  }
  return <RevealText by="lines" clip hidden={{ y: "105%" }} {...props} />;
}

export default LineReveal;
```

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