Scroll Reveal — <mv-reveal>

Reveal on scroll: children fade, blur, slide or zoom in with a stagger, once or every time; a pure-CSS variant is driven by a view timeline.

CategoryScroll
TypeWeb Component (<mv-reveal>)
Statusstable
Keywordsreveal, blur-fade, fade-in, stagger, in-view, view-timeline, scroll-driven

When to use

Avoid when

Install

node scripts/add.mjs scroll-reveal --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["scroll-reveal"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/element.js, core/motion.js, core/observe.js, components/scroll-reveal/scroll-reveal.js, components/scroll-reveal/scroll-reveal.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="mv-reveal-demo" class="mv-scroll-area" data-orientation="vertical" data-autohide tabindex="0" aria-label="Scroll reveal demo">
  <style>
    #mv-reveal-demo { width: min(100%, 46rem); height: 400px; margin-inline: auto; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: var(--mv-bg); }
    #mv-reveal-demo .in { padding: 2rem 1.75rem 7rem; display: grid; gap: 3.5rem; }
    #mv-reveal-demo .eyebrow { margin: 0 0 .5rem; font-size: .75rem; letter-spacing: .14em; text-transform: uppercase; color: var(--mv-accent-fg); font-weight: 600; }
    #mv-reveal-demo h2 { margin: 0; font: 700 clamp(1.6rem, 3.6vw, 2.3rem)/1.1 var(--mv-font-display); letter-spacing: -.03em; }
    #mv-reveal-demo .lead { margin: .75rem 0 0; color: var(--mv-fg-muted); max-width: 34rem; line-height: 1.55; }
    #mv-reveal-demo .hint { display: inline-flex; align-items: center; gap: .4rem; margin-top: 1.25rem; font-size: .8rem; color: var(--mv-fg-subtle); }
    #mv-reveal-demo .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .75rem; }
    #mv-reveal-demo .tile { padding: 1.1rem 1.2rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); }
    #mv-reveal-demo .tile h3 { margin: .8rem 0 .3rem; font-size: 1rem; }
    #mv-reveal-demo .tile p { margin: 0; font-size: .875rem; color: var(--mv-fg-muted); line-height: 1.5; }
    #mv-reveal-demo .ico { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--mv-radius-md); background: var(--mv-accent-subtle); color: var(--mv-accent-fg); }
    #mv-reveal-demo blockquote { margin: 0; padding: 1.5rem 1.75rem; border-radius: var(--mv-radius-xl); background: linear-gradient(135deg, var(--mv-accent-subtle), transparent 70%), var(--mv-bg-subtle); border: 1px solid var(--mv-border); font-size: 1.15rem; line-height: 1.5; letter-spacing: -.01em; }
    #mv-reveal-demo figcaption { margin-top: .75rem; font-size: .85rem; color: var(--mv-fg-muted); }
    #mv-reveal-demo .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: .75rem; }
    #mv-reveal-demo .stat { padding: 1rem; border-radius: var(--mv-radius-lg); background: var(--mv-bg-muted); text-align: center; }
    #mv-reveal-demo .stat b { display: block; font-size: 1.6rem; letter-spacing: -.03em; }
    #mv-reveal-demo .stat span { font-size: .78rem; color: var(--mv-fg-muted); }
    #mv-reveal-demo .code { font: 500 .72rem var(--mv-font-mono); color: var(--mv-fg-subtle); margin: 0 0 .6rem; }
    #mv-reveal-demo ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
    #mv-reveal-demo li { display: flex; gap: .75rem; align-items: center; padding: .8rem 1rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); font-size: .9rem; }
    #mv-reveal-demo li i { flex: none; width: .5rem; height: .5rem; border-radius: 50%; background: var(--mv-accent); }
    @media (max-width: 36rem) { #mv-reveal-demo .stats { grid-template-columns: repeat(2, 1fr); } }
  </style>
  <div class="in">
    <mv-reveal effect="blur" stagger="120">
      <p class="eyebrow">What’s new · September 2026</p>
      <h2>Everything you need to ship faster.</h2>
      <p class="lead">Each block appears the moment it enters the viewport — even inside a scrolling container like this one.</p>
      <span class="hint"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12l7 7 7-7"/></svg>Scroll down</span>
    </mv-reveal>

    <mv-reveal effect="fade-up" stagger="90" class="grid">
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg></span>
        <h3>Zero dependencies</h3>
        <p>Native Web Components, 3 KB on average, no framework required.</p>
      </article>
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span>
        <h3>Native animations</h3>
        <p>CSS scroll timelines, with an automatic JavaScript fallback.</p>
      </article>
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>
        <h3>Accessible</h3>
        <p>Respects reduced motion: content stays readable, without effects.</p>
      </article>
    </mv-reveal>

    <mv-reveal effect="slide-left">
      <figure style="margin:0">
        <blockquote>“We replaced three animation libraries with one. The site is faster and the design team is thrilled.”</blockquote>
        <figcaption>Chloe Russell — Frontend lead, Northwind Studio</figcaption>
      </figure>
    </mv-reveal>

    <div>
      <p class="code">&lt;mv-reveal effect="scale" repeat&gt; — replays every time it scrolls into view</p>
      <mv-reveal effect="scale" stagger="70" repeat class="stats">
        <div class="stat"><b>98</b><span>Lighthouse score</span></div>
        <div class="stat"><b>0</b><span>Dependencies</span></div>
        <div class="stat"><b>3 KB</b><span>Average size</span></div>
        <div class="stat"><b>AA</b><span>WCAG contrast</span></div>
      </mv-reveal>
    </div>

    <div>
      <p class="code">.mv-reveal-group data-effect="slide-right" — pure CSS, follows the scroll</p>
      <ul class="mv-reveal-group" data-effect="slide-right">
        <li><i></i>Native view() timelines, no JavaScript</li>
        <li><i></i>Reversible effect: scroll back up to see it rewind</li>
        <li><i></i>Content is simply visible in older browsers</li>
      </ul>
    </div>
  </div>
</div>

API

Attributes

NameTypeDefaultDescription
effectfade-up | fade-down | fade | blur | scale | slide-left | slide-rightfade-upEntrance preset. slide-left comes in from the left, slide-right from the right.
staggernumber (ms)90Delay between two direct children.
durationnumber (ms)700Reveal duration of each child.
delaynumber (ms)0Delay before the first child.
distanceCSS length1.5remStarting offset distance.
repeatbooleanfalseHides the children again when the block leaves, so it replays every time (otherwise it plays once).
thresholdnumber 0–10.2Visible fraction of the block that triggers the reveal.
marginrootMargin0px 0px -8% 0pxMargin of the trigger area.
scrollerselectorScrolling ancestor used as the root. Defaults to the viewport, clipped by every scrolling ancestor — so it works inside any container.
data-effectsame as effectPreset for the .mv-reveal / .mv-reveal-group CSS classes.

Methods

NameDescription
reveal()Shows all children immediately.

Events

NameDescription
mv-revealBlock revealed or hidden. detail: { visible }.

CSS classes

NameDescription
mv-revealPure CSS: the element appears in step with the scroll (animation-timeline: view()), reversible. Without support: simply visible.
mv-reveal-groupPure CSS: each child appears, staggered via sibling-index() when available.

CSS variables

NameDefaultDescription
--mv-reveal-distance1.5remOffset distance.
--mv-reveal-blur12pxStarting blur of the blur effect.
--mv-reveal-easingvar(--mv-ease-out)Easing of <mv-reveal>.
--mv-reveal-stagger / -duration / -delayCSS equivalents of the attributes.
--mv-reveal-iChild index, written by the JS (read-only).

Accessibility

Content stays in the DOM and readable by screen readers at all times; only its visual rendering is animated. Reduced motion: everything is shown immediately, without offset or blur. Before the JS runs (SSR), content is visible. Print: everything is visible.