Elevate your type dynamic
A responsive text animation library for React, built around a ready-to-use component with six interchangeable animation effects.
Up your game with text reveal
Text changes. Screens shrink. Fonts arrive late. Your animation should keep up. Txtrvl follows the browser’s actual visual lines, from two lines to ten, without splitting your text by hand.
Six effects. One component. Your choice.
Follows changes in text and container width
Adapts to font sizes and web font loading
Real line wrapping, newlines and long words
Your typography, colors and timing
Scroll triggers and React state controls
Reduced motion and accessible source text
TypeScript, Next.js, ESM and CommonJS
Live preview
Six effects, real browser line wrapping. Choose an effect, play it, then squeeze the width or change the words.
0 visual lines · Scroll trigger: hidden
The first animation starts when the text scrolls into view, then switches to Manual trigger. Use Play, Reset and Replay, or turn Manual trigger off to return to scrolling. Your reduced-motion preference is respected: text appears without transitions.
Installation
To install our component, you only need to go through these twenty steps. Kidding, there are only two.
Install
Install the library with npm install txtrvl. It includes a ready-to-use React component, six interchangeable effects and TypeScript types. React and React DOM 16.14.0 or newer are supported.
Use it
Use the named import: import { Txtrvl } from "txtrvl". Choose an effect with animation, set timing with duration and delayPerRow, and bring your own color and typography through style or className. The default effect is reveal.
import { Txtrvl } from "txtrvl";
export function Heading({ manualTextRevealState = false }) {
return (
<Txtrvl
text="Your words. Your timing."
animation="slide-up"
duration={1000}
delayPerRow={200} // ms between lines
className="animated-heading"
style={{ fontSize: "7rem", color: "red" }}
// Scroll visibility, not completion
onChange={(isVisible) => console.log(isVisible)}
manualTrigger={{ // optional config
isVisible: manualTextRevealState, // true | false
}}
scrollTrigger={{
root: null, // Viewport or ancestor
threshold: 0.5, // Visible area ratio
offsetY: 0, // Expand / shrink (px)
delay: 0,
// "above", "both" or "no-reset"
resetPolicy: "above",
disabled: false, // true: manual only
}}
/>
);
}One component, all the controls
Txtrvl 1.1.8 brings together the animation and layout work originally prepared for unreleased 1.1.7, plus the published entry-point and packaging updates. All six effects share the same API.
| Prop | Type | Default | What it does |
|---|---|---|---|
text | string | Required | Text to animate, including optional newlines. |
animation | TxtrvlAnimation | "reveal" | reveal, fade, slide-up, slide-left, blur or scale. |
duration | number | 1000 | Transition duration in milliseconds. |
delayPerRow | number | 200 | Delay between successive visual lines, in milliseconds. |
style | React.CSSProperties | — | Container dimensions, color and typography. |
className | string | — | Additional CSS class for the container. |
onChange | (isVisible: boolean) => void | — | Called when visibility detected by the scroll trigger changes. |
manualTrigger | { isVisible: boolean } | — | Control visibility through React state. |
scrollTrigger | Scroll configuration | Enabled | Options below control automatic visibility. |
TypeScript exports: TxtrvlProps and TxtrvlAnimation. React and React DOM 16.14.0 or newer are supported.
| Prop | Type | Default | What it does |
|---|---|---|---|
root | Element | null | null | Viewport by default; optionally supply an ancestor DOM element. |
threshold | number | 0.5 | Fraction of the maximum visible area that fits in the viewport and clipping containers. |
offsetY | number | 0 | Expand or shrink the vertical detection bounds, in pixels. |
delay | number | 0 | Wait this many milliseconds before changing visibility; cancelled if the condition changes. |
resetPolicy | "above" | "both" | "no-reset" | "above" | "above" resets when text goes below the visible area; "both" resets on either exit; "no-reset" keeps revealed text visible. |
disabled | boolean | false | Disable automatic triggering for manual playback alone. |
Scrolling uses IntersectionObserver and accounts for clipping by scrollable containers. For long text, the threshold uses the area that can fit inside the visible container. When both triggers are active, either the scroll trigger or manual state can reveal the text.
Play it yourself
For a button to control playback on its own, disable scrolling and pass your state through manualTrigger. Reset before playing again.
import { useState } from "react";
import { Txtrvl, type TxtrvlAnimation } from "txtrvl";
export function PlayableHeading() {
const [isVisible, setIsVisible] = useState(false);
const animation: TxtrvlAnimation = "fade";
return (
<>
<button onClick={() => setIsVisible((visible) => !visible)}>
{isVisible ? "Reset animation" : "Play animation"}
</button>
<Txtrvl
text="Your words. Your timing."
animation={animation}
scrollTrigger={{ disabled: true }}
manualTrigger={{ isVisible }}
style={{ width: "100%", maxWidth: "48rem" }}
/>
</>
);
}React, Next.js & your styles
Use the named import in ESM, or const { Txtrvl } = require("txtrvl") in CommonJS. Styles are included automatically; an explicit import "txtrvl/styles.css" is also available.
Published entry points include "use client" for Next.js App Router. Put callbacks and interactive React state in your own Client Component. Set colors, fonts and dimensions through style or className.
Need the source of truth? Read the README, public types or AI-friendly docs.
FAQ
No one ever asks us anything, so we had to write everything ourselves so the bot would have something to read.

