webdevpuneet.com

Hero with Animated Scroll Cue — Free HTML CSS JS Snippet

Hero with Animated Scroll Cue · Heroes · Plain HTML, CSS & JS · Live preview

CategoryHeroes

Copy & paste

Source Code

<section class="scu-hero" id="scuHero">
  <div class="scu-copy">
    <span class="scu-eyebrow">Field notes, issue 12</span>
    <h1 class="scu-h1">A quieter way<br>to build software</h1>
    <p class="scu-sub">No dashboards shouting at you. No red badges. Just the tools you need, exactly when you need them.</p>
    <a href="#" class="scu-cta">Read the story</a>
  </div>

  <button class="scu-cue" id="scuCue" aria-label="Scroll down">
    <span class="scu-cue-arrow">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
    </span>
    <span class="scu-cue-label">Scroll</span>
  </button>
</section>
<section class="scu-next">
  <p>You actually scrolled. The cue above already noticed and faded out.</p>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real scroll-position detection
Reads window.scrollY, not a fixed delay.
Passive scroll listener
Never blocks scroll performance.
Correct on load
evaluateScroll() runs once immediately, handling mid-scroll loads.
Clickable, not decorative
A real <button> that triggers a smooth scroll.
CSS bounce, JS visibility
Motion and interactivity concerns cleanly separated.
Configurable threshold
One constant controls when it disappears.
Accessible label
aria-label describes the control for screen readers.
Reappears on scroll-up
State reflects current position, not a one-time fade.

About this UI Snippet

Hero with Animated Scroll Cue — Fades Out on Real Scroll, Not a Timer

Screenshot of the Hero with Animated Scroll Cue snippet rendered live

Most "scroll cue" implementations are pure CSS: a bouncing arrow that loops forever regardless of what the visitor does. This snippet's cue bounces via CSS, but its visibility is driven by an actual scroll event listener reading window.scrollY — so it disappears the moment the visitor has genuinely started scrolling, and stays put if they haven't.

Real scroll detection, not a setTimeout guess

The temptation with a scroll cue is to hide it after a fixed delay ("fade out after 4 seconds") — but that fades it out for a visitor who's still reading the headline, and leaves it visible for one who scrolled instantly. This snippet instead attaches a passive: true scroll listener that checks window.scrollY against a small HIDE_THRESHOLD (40px) on every scroll event, so the cue's visibility is a direct function of how far the page has actually moved — not a clock.

Why { passive: true } matters

Scroll listeners can block the browser's scroll-performance optimizations unless marked passive, since the browser otherwise has to wait to confirm the listener won't call preventDefault(). Since this listener only reads scrollY and toggles a class — it never calls preventDefault() — marking it passive is both correct and free performance.

The bounce is CSS, the visibility is JS

The up-down bounce itself is a simple infinite @keyframes animation — that part genuinely can run forever with no downside, since it's purely decorative motion on an element that's either shown or hidden. The two concerns are cleanly separated: CSS owns "how it moves while visible," JavaScript owns "whether it's visible at all," which keeps the animation itself simple while the interactive logic stays testable.

A cue that also functions as a control

The scroll cue is a real <button>, not a decorative <div> — clicking it calls scrollIntoView({ behavior: 'smooth' }) on the hero's next sibling section, so it's an affordance you can act on directly rather than only a hint to scroll manually. It also has an aria-label for screen reader users.

Correct on load, not just after the first scroll

evaluateScroll() runs once immediately on script load (not just inside the listener), so a page that loads already scrolled — for example via a same-page anchor link — shows the cue in the correct hidden/visible state from the first paint instead of only updating on the next scroll event.

Customizing it

Adjust HIDE_THRESHOLD for a cue that disappears sooner or later, swap the arrow icon for a mouse-wheel glyph, or fade with an opacity transition instead of the visibility toggle. Pair it with scroll reveal grid or scroll zoom hero for more scroll-driven hero effects.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThe hero renders full-height with the cue bouncing at the bottom.
  2. 2
    Scroll down slightlyPast the 40px threshold, the cue fades out — this is a real scroll listener, not a timer.
  3. 3
    Scroll back to the topThe cue reappears since evaluateScroll() re-checks scrollY on every scroll event.
  4. 4
    Click the cue itselfIt performs a real smooth scroll to the next section.
  5. 5
    Tune the thresholdChange HIDE_THRESHOLD to control how much scroll hides the cue.
  6. 6
    Swap the iconReplace the chevron SVG with a mouse-wheel icon if preferred.

Real-world uses

Common Use Cases

Editorial landing pages
Signal that there is more below with a bouncing arrow that disappears once the visitor has actually scrolled, read from window.scrollY.
Portfolio heroes
Pair with a portfolio hero so the opening screen invites exploration, using a passive scroll listener that never blocks scrolling.
Single-page storytelling
Guide visitors into a scroll narrative, with a real button that triggers a smooth scroll to the next section.
Launch pages
Combine with a minimal hero for a clean first screen, with evaluateScroll() running once on load to handle mid-page refreshes.
Agency reels and documentation
Nudge visitors past a bold opener, or hint at content beneath a large documentation landing banner.
Related: Hero with 3D Tilting Product Mockup
See the Hero with 3D Tilting Product Mockup for a related heroes pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

On real scrolling. A scroll event listener reads window.scrollY on every scroll event and toggles a hidden class once it exceeds a small threshold (40px by default). There is no setTimeout involved — a visitor who never scrolls keeps seeing the cue indefinitely, and one who scrolls immediately sees it disappear immediately.

Passive listeners tell the browser upfront that the handler will never call preventDefault(), so the browser doesn't have to wait for the handler to finish before proceeding with the scroll — improving scroll smoothness, especially on mobile. Since this listener only reads scrollY and toggles a CSS class, it never needs to block the default scroll behavior, so passive: true is free performance with no downside.

evaluateScroll() is called once immediately after the listener is attached, not just inside future scroll events. So if a visitor arrives via a same-page anchor link or the browser restores a scroll position, the cue is shown or hidden correctly on the very first paint rather than waiting for the next scroll event to correct it.

It's a real <button> with a click listener that calls scrollIntoView({ behavior: "smooth" }) on the hero's next sibling section — so it functions as an actual scroll control, not only a visual hint. It also carries an aria-label for screen reader users, since an unlabeled icon-only button would otherwise be meaningless to assistive tech.

Attach the scroll listener in a mount effect (useEffect with an empty dependency array in React) and remove it in the cleanup function to avoid leaking listeners across re-renders. Keep the visibility state in a piece of component state (or toggle a class via a ref) driven by the same scrollY-vs-threshold check, and call evaluateScroll() once synchronously after mount for the same reason as the vanilla version.