Caption Crossfade Carousel — HTML CSS JS Snippet

Caption Crossfade Carousel · Heroes · Plain HTML, CSS & JS · Live preview

What's included

Features

Background and caption crossfade on genuinely independent transitions, not one shared opacity change
Caption text is swapped only while fully invisible — no jump-cut mid-fade
requestAnimationFrame forces a real paint of the hidden state before the fade-back-in begins
Heading and description have staggered transition delays for a more deliberate, less mechanical entrance
Autoplay via setInterval that cleanly resets on any manual navigation, dot or keyboard
Fully data-driven slide content from one array — no duplicated markup per slide

About this UI Snippet

Caption Crossfade Carousel — Two Independently-Timed Fades, Not One

Screenshot of the Caption Crossfade Carousel snippet rendered live

A naive crossfade carousel just swaps everything — image and text — at the same instant behind one opacity transition, which makes the caption feel like it's cutting rather than fading. This snippet treats the background image and the caption as *two separate transitions on two separate clocks*: the background crossfades via a plain CSS background transition, while the caption is explicitly hidden, has its text content swapped while fully invisible, and is only then faded back in — its heading and paragraph carrying their own staggered transition-delay values so the title settles slightly before the description does.

Why the text swap happens inside a requestAnimationFrame, not immediately

Removing .ccc-show (starting the caption's fade-out) and changing textContent in the very same synchronous line would mean the browser never actually paints the "faded out" state before the new text appears — the fade-out transition would have nothing to visibly transition from. Wrapping the text swap in requestAnimationFrame forces a real paint of the hidden state first, so the *subsequent* fade back in is genuinely animating from invisible-with-new-text, not skipping straight to visible.

Autoplay that resets cleanly on manual interaction

A setInterval drives automatic advancing, but every manual navigation — a dot click or an arrow key — calls resetTimer(), which clears and re-creates that interval. That's what stops a slow reader's manual click from being immediately overridden by an autoplay tick that was already halfway through its countdown before they interacted.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the caption's text content is changed inside a requestAnimationFrame callback rather than immediately after removing the visible class, and what visual artifact would appear if that were skipped. It's also worth asking the assistant to add a pause-on-hover behavior for the autoplay timer (similar to the Autoplay Progress-Bar Carousel snippet elsewhere in this library), or to add prefers-reduced-motion handling that disables both the crossfade and the autoplay for users who've requested reduced motion.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a hero carousel in plain HTML, CSS, and vanilla JavaScript where the background image and the caption text crossfade on two independently-timed transitions rather than a single shared fade — no library.

Requirements:
- A hero container with a background element that crossfades between different background styles using a plain CSS transition on the background property, and a separately-positioned caption overlay containing a heading and a description paragraph.
- The caption's heading and paragraph must each have their own opacity and transform-based fade-in transition, with the paragraph's transition-delay slightly longer than the heading's, so the two elements visibly settle into place at slightly different moments rather than simultaneously.
- On every slide change: first remove a "visible" state class from the caption (triggering its fade-out), then update the heading and paragraph's text content to the new slide's content only once that hidden state has actually been given a chance to paint (using requestAnimationFrame to defer the text change, not changing it in the same synchronous step as hiding the caption), and only after that text change re-add the "visible" state class to trigger the fade back in with the new content already in place.
- Automatic slide advancement on a fixed timer (e.g. every 4-5 seconds) using setInterval, where the JavaScript slide data (background style, heading text, description text) lives in a single array, not duplicated markup per slide.
- A row of dynamically generated indicator dots that jump directly to a specific slide when clicked, and Left/Right arrow key support for manual navigation.
- Any manual navigation (clicking a dot or pressing an arrow key) must reset the autoplay timer, so a manual interaction is not immediately followed by an autoplay-triggered change that was already mid-countdown.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="ccc-wrap" id="cccWrap">
  <div class="ccc-image" id="cccImage"></div>
  <div class="ccc-caption">
    <h2 id="cccTitle"></h2>
    <p id="cccText"></p>
  </div>
  <div class="ccc-dots" id="cccDots"></div>
</div>

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA hero appears with its background and caption both fading into view.
  2. 2
    Wait for autoplayEvery 4.5 seconds, the background crossfades while the caption independently fades out, changes, and fades back in.
  3. 3
    Click a dotJump directly to that slide — the autoplay timer resets so it doesn't interrupt too soon afterward.
  4. 4
    Use arrow keysLeft/Right navigate manually, also resetting the autoplay timer.
  5. 5
    Watch the stagger closelyNotice the heading finishes fading in slightly before the description — a deliberate delay offset, not a coincidence.

Real-world uses

Common Use Cases

Marketing hero carousels
A more polished, considered feel than a flat crossfade — the caption reads as composed, not overwritten.
Editorial or magazine-style rotators
Staggered caption entrance suits longform or editorial content better than an instant swap.
Portfolio case-study hero banners
Showcase several projects with a caption transition that feels deliberate and premium.
App feature announcement banners
Rotate through feature highlights with independent, staggered image/text transitions.

Got questions?

Frequently Asked Questions

Removing the visible class and changing textContent in the same synchronous step would mean the browser batches both changes into one paint — there's no visible "faded out" moment for the fade-back-in to animate from. Deferring the text change to the next animation frame forces the hidden state to actually render first.

Change the 4500 (milliseconds) passed to setInterval in two places — the initial timer creation and inside resetTimer, which must both use the same value to stay consistent.

The transition-delay values in the CSS (.15s for the heading, .28s for the paragraph) control how much later each element starts its fade-in relative to when .ccc-show is added — increase the gap between them for a more pronounced stagger.

Without resetTimer(), a manual navigation could be immediately followed by an autoplay tick that was already most of the way through its own countdown — resetting gives a full autoplay interval after every manual interaction, so a reader always gets a fair amount of time before the next automatic change.

Dots are real labeled buttons and the carousel is fully operable via Left/Right arrow keys; add prefers-reduced-motion handling to shorten or remove the fade transitions, and consider pausing autoplay entirely for users who've requested reduced motion.