Slide In Text for React
Animates text sliding in from the left with a fade when it enters the viewport.
Component
↓ scroll
Los Flamencos National Reserve
is a nature reserve located
in the commune of San Pedro de Atacama.
The reserve covers a total area
of 740 square kilometres.
"use client";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useLayoutEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface SlideInTextProps {
children: React.ReactNode;
className?: string;
left?: string;
y?: string;
opacity?: number;
ease?: string;
scrub?: boolean | number;
start?: string;
end?: string;
}
export function SlideInText({
children,
className,
left = "-200px",
y,
opacity = 0,
ease = "power3.out",
scrub = true,
start = "0px bottom",
end = "bottom+=400px bottom",
}: SlideInTextProps) {
const ref = useRef<HTMLParagraphElement>(null);
useLayoutEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const ctx = gsap.context(() => {
gsap.from(ref.current, {
ease,
left,
...(y !== undefined && { y }),
opacity,
scrollTrigger: {
end,
scrub,
start,
trigger: ref.current,
},
});
});
return () => ctx.revert();
}, [left, y, opacity, ease, scrub, start, end]);
return (
<p ref={ref} className={cn("relative", className)}>
{children}
</p>
);
}Installation
1. Install dependencies
pnpm add gsap2. Copy the component file
"use client";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useLayoutEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface SlideInTextProps {
children: React.ReactNode;
className?: string;
left?: string;
y?: string;
opacity?: number;
ease?: string;
scrub?: boolean | number;
start?: string;
end?: string;
}
export function SlideInText({
children,
className,
left = "-200px",
y,
opacity = 0,
ease = "power3.out",
scrub = true,
start = "0px bottom",
end = "bottom+=400px bottom",
}: SlideInTextProps) {
const ref = useRef<HTMLParagraphElement>(null);
useLayoutEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const ctx = gsap.context(() => {
gsap.from(ref.current, {
ease,
left,
...(y !== undefined && { y }),
opacity,
scrollTrigger: {
end,
scrub,
start,
trigger: ref.current,
},
});
});
return () => ctx.revert();
}, [left, y, opacity, ease, scrub, start, end]);
return (
<p ref={ref} className={cn("relative", className)}>
{children}
</p>
);
}3. Import and use
import { SlideInText } from "@/components/slide-in-text";
<SlideInText>Your text here</SlideInText>;Usage
Import the component
Add the SlideInText import to your file.
import { SlideInText } from "@/components/slide-in-text";Use with default props
Wrap your text in SlideInText for the default slide-in animation.
<SlideInText>Your text here</SlideInText>;Customize with props
Adjust left, scrub, start, and end to fine-tune the animation.
<SlideInText
left="-400px"
scrub={0.5}
start="top 80%"
end="bottom+=200px bottom"
>
Custom animated text
</SlideInText>;Guidelines
- Place SlideInText inside a scrollable container (e.g. your main content area or a div with overflow-y-auto).
- Use scrub: true for smooth scroll-linked animation, or a number (e.g. 0.5) for a slight delay.
- Adjust left to control how far the text slides in from (e.g. -200px, -400px).
- Customize start and end to change when the animation triggers relative to the viewport.
Props
All props are optional unless marked required. Use these to customize every aspect of the component.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | - | Content to render inside the paragraph. |
| className | string | - | Additional CSS classes applied to the element. |
| left | string | "-200px" | CSS left offset to animate from. Passed directly to GSAP. |
| y | string | - | CSS y offset to animate from (e.g. '20px'). Omit for no vertical animation. |
| opacity | number | 0 | Initial opacity before animation (0–1). |
| ease | string | "power3.out" | GSAP ease applied to the scrub. |
| scrub | boolean | number | true | ScrollTrigger scrub value. true = smooth, number = seconds to catch up. |
| start | string | "0px bottom" | ScrollTrigger start position. |
| end | string | "bottom+=400px bottom" | ScrollTrigger end position. |
Accessibility
- Decorative scroll-driven text reveal with no interactive controls or focusable elements; it renders a single non-interactive paragraph.
- The text content stays in the DOM and is read by screen readers regardless of animation state, since only visual styles are tweened.
- There are no buttons, inputs, links, keyboard handlers, ARIA roles, or focus management, so there is nothing to make keyboard operable here.
- The component does not check prefers-reduced-motion or matchMedia, so it should be guarded to disable or skip the slide and fade for users who prefer reduced motion.
Performance
- It animates opacity, which is cheap and GPU-compositable, but it also animates the left CSS property, which forces layout and paint on each scroll-driven frame and is more expensive than animating transform.
- Passing the optional y prop animates a transform translate, which is GPU-friendly; preferring transform over left would keep the motion on the compositor.
- ScrollTrigger drives the tween from scroll position and internally throttles updates with requestAnimationFrame, so it avoids running work on every raw scroll event.
- Cleanup is handled by gsap.context() with ctx.revert() in the useLayoutEffect return, which removes the ScrollTrigger instance and tween on unmount or when props change, preventing leaked listeners.
- There is no will-change hint and no prefers-reduced-motion gating, so the scroll-linked animation always runs even for users who would prefer it disabled.
Examples
Basic
Default usage with standard scroll range and ease.
↓ scroll
Los Flamencos National Reserve
is a nature reserve located
in the commune of San Pedro de Atacama.
The reserve covers a total area
of 740 square kilometres.
import { SlideInText } from "@/components/slide-in-text";
export function SlideInTextBasic() {
return (
<div className="space-y-8">
<SlideInText>First line of text</SlideInText>
</div>
);
}Custom offset
Slide further with a faster scrub for snappier animation.
↓ scroll
Los Flamencos National Reserve
is a nature reserve located
in the commune of San Pedro de Atacama.
The reserve covers a total area
of 740 square kilometres.
import { SlideInText } from "@/components/slide-in-text";
export function SlideInTextCustomOffset() {
return (
<SlideInText left="-400px" scrub={0.5}>
Second line
</SlideInText>
);
}Custom scroll range
Control when the animation starts and ends relative to viewport.
↓ scroll
Los Flamencos National Reserve
is a nature reserve located
in the commune of San Pedro de Atacama.
The reserve covers a total area
of 740 square kilometres.
import { SlideInText } from "@/components/slide-in-text";
export function SlideInTextCustomRange() {
return (
<SlideInText start="top 80%" end="bottom+=200px bottom">
Third line
</SlideInText>
);
}Related reading
- Choreographing Multi-Step Motion — Staggering words in with keyframes
- Invisible Scissors — Revealing text with clip-path masks
- Easing Curves That Feel Natural — Easing that makes text entrances feel smooth
Last updated on Jun 19