Scroll✨ Premium

Text Reveal

Four title + subtitle blocks in a 280 px box: each word (1.5 rem, weight 800) starts at translateY(100%) below a line set to overflow: clip and rises during the first 40% of its entry into the box — 11 px of scrolling — then its subtitle goes from 0 to 1 over the 10 → 50% of its own entry, 37 px lower. Two CSS animations on animation-timeline: view(), zero JavaScript.

CSSTextReveal

Updated

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Rooms of an exhibition — a museum's web audioguide on a smartphone — Text Reveal example 1

① Rooms of an exhibition — a museum's web audioguide on a smartphone

WhenThe audioguide page open on the visitor's phone: Antiquities, Drawings Room, Glass Gallery, Sculptures, Storerooms — each room name rises out of its line as the visitor scrolls down, followed by its one-line introduction.
WhyThe room name emerges crisp from a mask, like an engraved plate, then the detail line follows: the title → detail hierarchy is encoded in the two ranges and in the 36 px between their entries. fx-0569 lights words up in place, without that delay; a global fade (fx-0535) would show name and sentence as one block.
Settings250 × 340 px phone, 262 px box under the header, stage set to height: auto with 110 px of bottom padding (372 px of travel), 20 px words, 11.5 px subtitles in white at 55%, a static “Room n” label above each line; ranges widened to entry 0% entry 100% (25 px of travel per word, 16 per subtitle), the sold code's 40% being too brief for a capture. The automatic scrolling is a demo device.
Legs of a triathlon — a sports organizer's results page — Text Reveal example 2

② Legs of a triathlon — a sports organizer's results page

WhenThe “Legs” tab of the results page: Swim, Bike, Run, then Finish, each in 44 px capitals rising out of its line, with the distance and the best time of the year underneath.
WhyThree short, strong words: the line mask keeps the motion clean (nothing sticks out above the line during the rise), and the numbered subtitle arrives after the word; a global fade (fx-0535) would show word and figures as one block.
Settings302 px box under a 58 px bar, words in 44 px capitals at weight 900 with letter-spacing: -.03em, 13 px subtitles in white at 70% with the time in a <b> colored #5eead4, four blocks, 150 px of bottom padding (326 px of travel), ranges entry 0% entry 100% (45 px of travel per word).
Headlines of an online student newspaper — editorial home page — Text Reveal example 3

③ Headlines of an online student newspaper — editorial home page

WhenThe paper's front page on a paper-toned background: four headlines (Campus, Culture, Sport, Debate) at 1.5 rem rise out of their line one after the other as you scroll, each followed by its standfirst and preceded by its page number.
WhyA headline is assembled: the title first, the standfirst next; the two offset ranges do that editing, and the mask, which carries the rule here, brings each title up from under its rule without overlapping the previous headline. Words already in place that light up (fx-0569) would leave the rule sitting on a motionless title.
Settings#f4f1ea background and dark text (word #1a1a1a, standfirst #3a3732) instead of the black frame, Georgia for both, word at 1.5 rem weight 700, standfirst at 13 px within 440 px, a border-top: 1px solid #d9d4c8 rule on the mask line, 292 px box, 120 px of bottom padding (284 px of travel), ranges entry 0% entry 100% (28 px per headline).

How it works

The sold HTML: a .demo-preview frame (centered flex, min-height: 300px, position: relative, #0a0a0f background, white text, font-family: sans-serif), the .sdc-scroll-container box (100% × 280px, overflow-y: auto, scroll-behavior: smooth, a 4 px scrollbar styled with ::-webkit-scrollbar, purple thumb at 40%), the .sdc-textreveal-content stage (900 px tall, padding: 80px 24px, flex column, gap: 40px) and four divs each holding a .sdc-textreveal-line (overflow: clip) that masks a span.sdc-textreveal-word (inline-block, 1.5 rem = 24 px, weight 800, white, 28 px tall) and a p.sdc-textreveal-sub (0.82 rem = 13 px, white at 40%, margin-top: 8px). Below the box, span.sdc-scroll-hint “Scrollez ↓” (absolute, 8 px from the bottom of the frame, 10.4 px, white at 30%) pulses from .3 to .7 every 2 s. Measured on a blank page: travel 900 − 280 = 620 px, words at 80, 184, 288 and 392 px from the top of the stage — a 104 px pitch, because the p's default bottom margin (13 px) adds to the block's 28 + 8 + 15 px and the 40 px gap. No JavaScript, no id, no CSS variable, no console error.

Both texts share the same setup: animation-timeline: view(), animation-duration: auto, animation-fill-mode: both, linear easing. view() gives each element an anonymous timeline read on the nearest scroll container — here the box — and graduated on the element's crossing of its frame; animation-range picks the segment that drives the keyframes. The word starts at translateY(100%) (100% of its own height, 28 px, below the line that clips it) and opacity: 0, and reaches translateY(0) / 1 over entry entry 40%: from the moment its top edge touches the bottom of the box until 40% of its entry, i.e. 11.2 px of scrolling. Measured in 8 px steps in Chromium 151: the third word is at 0 / 28 px at scrollTop 8, .69 / 8.6 px at 16, 1 / 0 at 24; the fourth makes the same trip between 112 and 128. The subtitle has no starting transform (the translateY(0) in sdcFadeIn changes nothing): it goes from 0 to 1 over entry 10% entry 50%, 40% of its 15 px, i.e. 6 px (measured .38 at 48, 1 at 56 for the third; .35 at 152, 1 at 160 for the fourth). The title → detail offset comes from geometry more than from the percentages: the subtitle enters the box 36 px after the word (28 px of word + 8 px of margin), and its 10% add only 1.5 px. Past scrollTop 160 everything is visible; 460 px of empty travel remain.

Until 30 September 2026 the line was set to overflow: hidden, and the mask never opened: an element with overflow: hidden is a scroll container, so view() attached to the line itself, whose frame the word fills from the start. Measured on that code: getAnimations()[0].timeline.source = .sdc-textreveal-line, animation finished and frozen at 20%, all four words at opacity 1 with no transform at rest — even the one sitting at 392 px, outside the box. Only the subtitles, direct children of the blocks, attached to the box and appeared. overflow: clip clips identically without creating a scroll container: the source becomes .sdc-scroll-container and the word rises out of its line. Other traits of the code: nothing is keyed by id, every word carries its own timeline, so several copies coexist on a page and a fifth block needs no extra CSS; scroll-behavior: smooth only eases programmatic scrolls; the font is the system sans-serif (nothing is loaded); and the JavaScript field of the sold code is empty.

Accessibility

  • prefers-reduced-motion not in the code: measured under the preference in Chromium, the third word still goes from 0 to 1 between scrollTop 8 and 24, the hint pulses (animation-play-state: running) and scroll-behavior stays smooth. The motion follows the gesture and hides nothing lasting — a block that has entered the box stays displayed — which makes it an interaction-triggered animation (WCAG 2.3.3). For a motionless fallback: @media (prefers-reduced-motion: reduce) { .sdc-textreveal-word, .sdc-textreveal-sub { animation: none; opacity: 1; transform: none } .sdc-scroll-hint { animation: none } .sdc-scroll-container { scroll-behavior: auto } }.
  • Keyboard: measured, Tab focuses the box without a tabindex in Chromium and Firefox (Arrow Down: 40 and 51 px, PageDown: 285 and 303 px — the reveal follows), not in WebKit (focus stays on body, arrows do nothing). Add tabindex="0", an aria-label (“Four words, vertical scrolling”) and a :focus-visible style on .sdc-scroll-container. With a finger, the gesture must start inside the box; overscroll-behavior: contain keeps the page from being dragged along at the end of the travel.
  • Contrast: the white word on #0a0a0f measures 19.8:1. The subtitle at 40% white reads 3.8:1 at 13 px — below the 4.5:1 required for text of that size; 60% (rgba(255,255,255,.6)) gives 7.3:1. The hint reads 2.6:1 static, 1.2:1 to 1.8:1 under the pulse (the .3 to .7 opacity multiplies the color's 30% white): decorative, give it aria-hidden="true". The frame's #0a0a0f background is part of the code: on a white page, remove it and the white text vanishes (1:1) — change the three colors then, as the third scene does on light paper.
  • Screen readers and structure: opacity: 0 removes nothing from the accessibility tree, the eight texts are read in order before and after scrolling, and nothing is announced along the way — there is nothing to announce. The word is a span in a div, the subtitle a p: no heading in the page outline. For room or leg names, put the word in an h2 or h3 (the class is enough, the tag is free) and keep the p. The “Scrollez ↓” label is hard-coded in French in the HTML, whatever the page language.

Browser compatibility

CSS only: animation-timeline: view(), animation-duration: auto, animation-range with the entry keywords — scroll-driven animations, which landed in Chrome and Edge 115 (July 2023) and Safari 26 (September 2025). Firefox renders them in no version: measured in Firefox 153, CSS.supports('animation-timeline: view()') returns false, animation-duration: auto is rejected (computed duration 0 s) and the animation, finished on a document timeline, applies its last keyframe — all eight texts are shown at opacity 1, with no transform, from rest, and the box scrolls normally (620 px of travel). A Firefox visitor therefore sees a static, complete list, without the slide. ::-webkit-scrollbar is ignored by Firefox (native scrollbar). Zero dependencies, zero script.

Chrome 115+✓ Full
Edge 115+✓ Full
Safari 26+✓ Full (measured in WebKit 26.5: same values as Chromium)
Firefox (all versions)✓ Static: all text displayed, no slide
Mobile iOS 26+✓ Full (touch scrolling inside the box)
Android Chrome 115+✓ Full

Where animation-timeline and animation-duration: auto are unknown (Firefox, Safari before 26, Chrome before 115), both declarations are dropped, the animation lasts 0 s and animation-fill-mode: both freezes it on its last keyframe: opacity 1, translateY(0). The code has no @supports block and needs none, because the final state is the readable state; it would need one — @supports not (animation-timeline: view()) { … } — if you inverted the keyframes (text that fades out) or if the end of sdcTextReveal stopped being the reading state. Without overflow: clip (Chrome before 90, Firefox before 81, Safari before 16), the line no longer clips anything, but no word is translated there: nothing changes on screen.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sdc-textreveal-content">
      <div>
        <div class="sdc-textreveal-line">
          <span class="sdc-textreveal-word">…</span>
        </div>
        <p class="sdc-textreveal-sub">…</p>
      </div>
      <div>
        <div class="sdc-textreveal-line">
          <span class="sdc-textreveal-word">…</span>
        </div>
        <p class="sdc-textreveal-sub">…</p>
      </div>
      <div>
        <div class="sdc-textreveal-line">
          <span class="sdc-textreveal-word">…</span>
        </div>
        <p class="sdc-textreveal-sub">…</p>
      </div>
      <div>
        <div class="sdc-textreveal-line">
          <span class="sdc-textreveal-word">…</span>
        </div>
        <p class="sdc-textreveal-sub">…</p>
      </div>
    </div>
  </div>
  <span class="sdc-scroll-hint">…</span>
</div>
Unlock the full code — €9.90/mo excl. tax

The full HTML + CSS + JS of Text Reveal, ready to paste — plus all 811 effects. Or €299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Word range (CSS — .sdc-textreveal-word { animation-range: entry entry 40% }) entry 0% → entry 40% (11.2 px for a 28 px word) The percentages are those of the word's entry into the box, hence proportional to its height, not the box's. entry 0% entry 100% stretches the rise over the whole entry (28 px, the setting of the three scenes); entry 0% cover 30% extends it until the word has climbed 30% of the box (about 92 px).
Subtitle range (CSS — .sdc-textreveal-sub { animation-range: entry 10% entry 50% }) entry 10% → entry 50% (6 px for a 15 px height) The lag behind the word comes mostly from the 36 px between their entries; the 10% add only 1.5 px. For a real pause, delay the start (entry 100% cover 30%) or raise margin-top.
Word start (CSS — transform: translateY(100%) and opacity: 0) 100% of its height (28 px), invisible The word starts fully below its line. translateY(60%) lets a third of it show before any scrolling; opacity: 1 in the base rule gives a pure mask reveal, without fade — the final keyframe stays valid.
Box and stage (CSS — .sdc-scroll-container { height: 280px }, .sdc-textreveal-content { height: 900px }) 280 px / 900 px (620 px of travel, everything revealed at 160 px) The stage is fixed at 900 px whatever the content: 460 px of empty travel after the last block. height: auto with a padding-bottom of at least half the box lets the last block enter completely (the scenes: 110 to 150 px).
Size and colors (CSS — font-size: 1.5rem, color: #fff; .sdc-textreveal-sub: .82rem, rgba(255,255,255,.4)) 24 px / 800 / white; 13 px / white at 40% The range follows the word's height: at 44 px (scene 2), the 40% make 18 px of travel. The subtitle at 40% reads 3.8:1 on #0a0a0f; 60% rises to 7.3:1. On a light background, change the three colors and drop the frame's background.
Line mask (CSS — .sdc-textreveal-line { overflow: clip }) clip Keep it as is: hidden would make the line a scroll container and view() would attach to it — the words would stop moving (that was the code's defect before 30/09/2026). The line takes a rule (border-top, scene 3) or a padding-top without breaking anything.
Hint and scrollbar (CSS — .sdc-scroll-hint, ::-webkit-scrollbar) “Scrollez ↓” 8 px from the bottom, 4 px bar The hint anchors to the .demo-preview frame (position: relative): without that frame it drops to the bottom of the page — delete the span and the @keyframes sdcHintPulse. The bar only concerns Chrome and Safari; scrollbar-width: thin covers Firefox.

FAQ

That is the symptom of a line set to overflow: hidden — the code sold until 30 September 2026, and still the copy displayed on the category page. An element with overflow: hidden is a scroll container; view() takes the nearest one, hence the line, where the word fills the whole frame: the animation is over before the first gesture (measured: timeline source = .sdc-textreveal-line, state finished frozen at 20%, four words at opacity 1 at rest). The rule applies to any ancestor between the word and the box: a card with overflow: hidden, an accordion, a carousel capture the timeline the same way. Replace it with overflow: clip (or clip-path: inset(0)), which clips without creating a container, and check with el.getAnimations()[0].timeline.source that the source really is the box.
Because animation-range: entry entry 40% is measured on the word's entry, that is on its own height: 40% of 28 px make 11.2 px of scrolling, and 6 px for the subtitle. A 100 px wheel notch crosses them in one or two frames; on a trackpad or with a finger, the slide shows. Measured in 8 px steps: 0 → .69 → 1 over three readings. To stretch it, widen the range (entry 0% entry 100%: 28 px, the choice of the three scenes; entry 0% cover 30%: about 92 px, the word finishes rising when it is already well inside the box) or enlarge the word — the range grows with it. Leave animation-duration: auto alone: with a scroll timeline, the range stands in for the duration.
The engine is the same — animation-timeline: view(), animation-duration: auto, animation-range, zero JavaScript, Firefox flat in both cases — but not the gesture. fx-0569 lines up 43 inline-block words in a 300 px paragraph and animates only their color: from white at 15% to #e0e7ff (the six accents from purple at 20% to #a78bfa) over entry cover 40%, a single range for all. The words are in place from the start and light up as they slide toward the top of the box — a reading that brightens. fx-0560 works in pairs: a word that moves (translateY(100%) → 0) behind a line mask, then a subtitle that appears on a second range, 36 px lower. It makes four titles with their detail, where fx-0569 makes running text. Pick fx-0569 for a paragraph to read, fx-0560 for a list of short titles each followed by its detail.