# In-view title

> Bring a section title in as it scrolls into view.

- Type: Component (reveal)
- Page: https://textlab.dev/components/in-view-title
- Markdown: https://textlab.dev/components/in-view-title.md
- Registry item: https://textlab.dev/r/in-view-title.json
- Source file: `registry/textlab/in-view-title/in-view-title.tsx`
- Dependencies: motion
- Variants: blur, words, lines, wipe
- Keywords: in view, scroll, section title, heading, reveal on scroll

## When to use

- Section headings that should animate as the reader arrives.
- Pages where every section title needs the same entrance.

## When not to use

- Hero copy that animates on load; use blur-reveal with trigger="mount".
- A paragraph that follows scroll progress; use scroll-fill.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/in-view-title
pnpm dlx shadcn@latest add @textlab/in-view-title
```

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/in-view-title.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 { InViewTitle } from "@/components/textlab/in-view-title/in-view-title";

export function Example() {
  return <InViewTitle variant="words" text="Make every word count" />;
}
```

## API reference

### InViewTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"blur" \| "words" \| "lines" \| "wipe"` | `"blur"` | blur suits most headings; words and lines rise from a clip; wipe uncovers left to right. |
| `as` | `"h1" \| "h2" \| "h3" \| "h4"` | `"h2"` | Heading level. |
| `once` | `boolean` | `true` | In view: play once instead of on every entry. |
| `amount` | `number` | `0.45` | Share of the title that must be visible before it starts. |
| `delay` | `number` | `0` | Seconds before the first part moves. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |
| `id` | `string` | – | Forwarded to the heading, for anchor links. |

## Accessibility

- Renders a real heading tag with the full text for screen readers.
- Choose as to match the page outline.

## Motion

- Starts when 45% of the title is visible.
- Titles already scrolled past show immediately.
- Hiding with once={false} is a quick fade with no stagger.

## Reduced motion

The title appears instantly with no blur, clip or mask.

## Responsive behavior

- Words, blur and wipe wrap naturally; lines are re-measured on resize.

## Performance

- One IntersectionObserver per title.
- Fine for headings, not long body text.

## 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: blur, words, lines, wipe.

## Related

- [Blur reveal](https://textlab.dev/components/blur-reveal.md): Words sharpen into focus.
- [Word reveal](https://textlab.dev/components/word-reveal.md): Words rise out of a clip.
- [Wipe reveal](https://textlab.dev/components/wipe-reveal.md): A soft edge uncovers the line.

## Also in reveal

- [Line reveal](https://textlab.dev/components/line-reveal.md): Lines lift in, re-measured on resize.
- [Character reveal](https://textlab.dev/components/char-reveal.md): Letters arrive one at a time.
- [Split text](https://textlab.dev/components/split-text.md): Stagger anything by chars, words or lines.
- [Tracking reveal](https://textlab.dev/components/tracking-reveal.md): Letter spacing breathes open as it appears.

## Source

### components/textlab/in-view-title/in-view-title.tsx

```tsx
"use client";

import type { CSSProperties } from "react";
import { BlurReveal } from "../blur-reveal/blur-reveal";
import { LineReveal } from "../line-reveal/line-reveal";
import { WipeReveal } from "../wipe-reveal/wipe-reveal";
import { WordReveal } from "../word-reveal/word-reveal";
import type { ReducedMotion } from "../lib/use-reduced";

export type InViewTitleVariant = "blur" | "words" | "lines" | "wipe";

export interface InViewTitleProps {
  text: string;
  /** blur suits most headings; words and lines rise from a clip; wipe uncovers left to right. */
  variant?: InViewTitleVariant;
  as?: "h1" | "h2" | "h3" | "h4";
  /** Set false to replay on every entry. */
  once?: boolean;
  /** Share of the title that must be visible before it starts, 0 to 1. */
  amount?: number;
  delay?: number;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
  id?: string;
}

/**
 * A section title that reveals itself as it scrolls into view, in four styles that share timing.
 * Titles already above the viewport show at once, and server-rendered titles appear on their own if
 * scripts are slow.
 */
export function InViewTitle({ text, variant = "blur", as = "h2", once = true, amount = 0.45, delay = 0, reducedMotion, className, style, id }: InViewTitleProps) {
  const shared = { text, as, once, amount, delay, reducedMotion, className, style, id, trigger: "inView" as const };
  if (variant === "words") return <WordReveal {...shared} />;
  if (variant === "lines") return <LineReveal {...shared} />;
  if (variant === "wipe") return <WipeReveal {...shared} />;
  return <BlurReveal {...shared} />;
}

export default InViewTitle;
```

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