Definition
A hero carousel is a horizontal slideshow of images or video and has controls that allow users to advance through each slide both forward and backward. Hero carousels visually enhance pages and set the tone for websites or key landing pages by offering multiple images, video, and text paired with call-to-action buttons, increasing user engagement. Hero carousels span the full width of a page and can be positioned at the top, center, or bottom of the page.
Usage and Specifications
Incorporate these best practices when working with carousels:
- Engage visitors with high-resolution images, not to exceed 72 ppi, minimum width of 1,920 pixels to accommodate large monitors
- Use a maximum of 5 slides
- If using autoplay, allow 5 seconds per slide
- User controls should always be available and legible
- Maintain consistent aspect ratio
- Provide alt text to support web accessibility
- Every page does not need a hero
- Buttons should follow alignment of body copy: centered or left- or right-aligned
Size
- Maximum: 300 ppi at full page/screen width
- Minimum: 1,920 pixels at 72 ppi for full page/screen width
Core Accessibility Principles
To meet the University accessibility standard, hero carousels must:
- Be fully operable using a keyboard
- Provide meaningful announcements for screen readers
- Use sufficient color contrast and visible focus styles
- Offer clear navigation, orientation, and labeling
- Avoid automatic motion unless it is user-controlled
- Structure and Markup
- Wrap the carousel in a
<section>or<div>withrole="region"and anaria-labeldescribing its purpose. - Group each slide in a container with role="group" and aria-roledescription="slide".
- Add aria-label attributes (e.g., “Slide 1 of 5, Title”) to communicate position and context.
- Wrap the carousel in a
- Controls
- Provide Previous/Next buttons with descriptive labels
- Ensure buttons are focusable and have visible focus outlines.
- Include a Pause/Play button for any auto-advancing carousel.
- Avoid tiny hit areas; use minimum 24 x 24 pixels clickable targets (icons, etc.) but total clickable area should be no less than 44 x 44 pixels.
- Keyboard Interaction
- Users must be able to move between slides using Tab and Enter/Space.
- Optionally, support arrow keys (left/right) for slide navigation.
- Keep focus movement predictable — do not trap users inside the carousel.
- Screen Reader Support
- Each slide should have a unique heading or label.
- Hide inactive slides from screen readers using aria-hidden="true".
- Motion and Timing
- Avoid auto‑rotation by default. If necessary, provide Pause/Play button.
- Avoid animations that flash more than 3 times per second to prevent seizure risks.
- Visual Design
- Provide strong color contrast (At least 4.5:1) between text and background.
- Ensure consistent visible focus indicators (e.g., a 2-pixel outline in a high-contrast color).
- Use responsive layouts; carousels must work on desktop and mobile.
- Be sure to use a different symbol or icon to represent each function, e.g., chevrons for advancing slides, carats for play, parallel lines for pause, etc.
- Provide content managers the ability to choose from a select set of background colors for overlays, e.g., black and white, or gray and blue
When to Use
- Setting the tone or mission for a site or key landing page
- Giving prominence to dynamic or featured content
- Encouraging exploration and click-throughs to pages with detailed information
When Not to Use
- When there is competing information of equal importance
- Not for long-form content; videos should be no more than 10 seconds
- Ensure there is enough content beneath the hero carousel to support a full page
- Do not use a hero carousel to fill up space on a page
Examples
The default color of the overlay is white but can be black. Alignment can be left, center, or right and must be set for each slide. Multiple slides autoplay with indicators and user controls and swipe on mobile.
- Overlay Color: white (default) or black
- Alignment: Set on each slide: hero-carousel__slide--align-left (default) hero-carousel__slide--align-center, hero-carousel__slide--align-right
- Autoplay: Add class autoplay and set data-autoplay="6000" (ms)
<!-- Demo 1: White overlay, left align (default), 3 slides -->
<section class="demo-section" aria-labelledby="demo1-title">
<div id="demo1-title" class="container">
<h2>White overlay (default)</h2>
<p>Three slides, auto-play.</p>
<ul>
<li><strong>Overlay color</strong>: <code>hero-carousel--overlay-white</code> (default), <code>hero-carousel--overlay-black</code>.</li>
<li><strong>Alignment</strong>: Set on each slide: <code>hero-carousel__slide--align-left</code> (default), <code>hero-carousel__slide--align-center</code>, <code>hero-carousel__slide--align-right</code>.</li>
<li><strong>Auto-play</strong>: Add class <code>autoplay</code> and set <code>data-autoplay="6000"</code> (ms).</li>
</ul>
</div>
<div class="hero-carousel hero-carousel--overlay-white autoplay" id="hero1" data-autoplay="5000" aria-roledescription="carousel">
<div class="hero-carousel__track">
<div class="hero-carousel__slide hero-carousel__slide--align-left" data-slide-type="video" data-video-provider="youtube" data-video-id="ScMzIvxBSi4" data-video-start="0" data-video-end="10" data-video-length-ms="10000" role="group" aria-roledescription="slide" aria-label="Slide 1 of 4">
<div class="hero-carousel__image-wrap hero-carousel__image-wrap--video" aria-hidden="true">
<iframe
class="hero-carousel__video"
title="Background video"
loading="lazy"
allow="autoplay; fullscreen; picture-in-picture"
referrerpolicy="strict-origin-when-cross-origin"
></iframe>
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Video</span>
<h2 class="hero-carousel__title"><a href="#">A 10-second campus highlight</a></h2>
<p class="hero-carousel__description">Muted, chrome-less background video. Auto-advance plays the full clip, then moves on.</p>
<div class="hero-carousel__actions">
<a href="#watch" class="btn btn--primary">Learn More</a>
<a href="#about" class="btn btn--secondary">About the Clip</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-left" role="group" aria-roledescription="slide" aria-label="Slide 2 of 4">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/View_of_Columbia_University.jpg/1280px-View_of_Columbia_University.jpg" alt="View of Columbia University campus with Low Memorial Library">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Campus</span>
<h2 class="hero-carousel__title"><a href="#">Low Library and the Morningside Heights Campus</a></h2>
<p class="hero-carousel__description">The heart of Columbia University's Morningside Heights campus features Low Memorial Library and the iconic steps. Explore tours, events, and history.</p>
<div class="hero-carousel__actions">
<a href="#visit" class="btn btn--primary">Plan a Visit</a>
<a href="#tours" class="btn btn--secondary">Campus Tours</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-center" role="group" aria-roledescription="slide" aria-label="Slide 3 of 4">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/2014_Columbia_University_Low_Memorial_Library_from_front.jpg/1280px-2014_Columbia_University_Low_Memorial_Library_from_front.jpg" alt="Low Memorial Library, Columbia University">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Research</span>
<h2 class="hero-carousel__title"><a href="#">TRAIL Research Salon Series</a></h2>
<p class="hero-carousel__description">Workshops, lectures, and panel discussions bringing together researchers and practitioners. Join us for the next session.</p>
<div class="hero-carousel__actions">
<a href="#register" class="btn btn--primary">Register Today</a>
<a href="#schedule" class="btn btn--secondary">View Schedule</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-right" role="group" aria-roledescription="slide" aria-label="Slide 4 of 4">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Columbia_University_%285678520550%29.jpg/1280px-Columbia_University_%285678520550%29.jpg" alt="Columbia University campus with Butler Library">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Admissions</span>
<h2 class="hero-carousel__title"><a href="#">Graduate Programs: Apply for Fall 2026</a></h2>
<p class="hero-carousel__description">Applications open for master's and doctoral programs across the arts, sciences, and professional schools.</p>
<div class="hero-carousel__actions">
<a href="#apply" class="btn btn--primary">Apply Now</a>
<a href="#programs" class="btn btn--secondary">Explore Programs</a>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="hero-carousel__prev" aria-label="Previous slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Prev</span>
</button>
<button type="button" class="hero-carousel__next" aria-label="Next slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Next</span>
</button>
<div class="hero-carousel__indicators">
<div class="hero-carousel__indicators-inner">
<button type="button" class="hero-carousel__play-pause" aria-label="Pause carousel" title="Pause">
<span class="hero-carousel__play-pause-progress" aria-hidden="true"></span>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--pause" viewBox="0 0 10 12" aria-hidden="true"><rect width="3" height="12" x="0" rx="1"/><rect width="3" height="12" x="7" rx="1"/></svg>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--play" viewBox="0 0 8 10" aria-hidden="true"><path d="M0 0v10l8-5z"/></svg>
</button>
<div role="tablist" class="hero-carousel__pips" aria-label="Carousel slides">
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="true" aria-current="true" aria-label="Slide 1" data-index="0"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 2" data-index="1"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 3" data-index="2"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 4" data-index="3"><span></span></button>
</div>
</div>
</div>
</div>
</section>
<!-- Demo 2: Black overlay, center align -->
<section class="demo-section" aria-labelledby="demo2-title">
<div id="demo2-title" class="container">
<h2>Black overlay</h2>
</div>
<div class="hero-carousel hero-carousel--overlay-black" id="hero2" data-autoplay="5000" aria-roledescription="carousel">
<div class="hero-carousel__track">
<div class="hero-carousel__slide hero-carousel__slide--align-center" role="group" aria-roledescription="slide" aria-label="Slide 1 of 2">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://images.unsplash.com/photo-1481627834876-b7833e8f5570?w=1280&h=549&fit=crop" alt="Library interior with books">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Libraries</span>
<h2 class="hero-carousel__title"><a href="#">Butler Library: Collections and Study Spaces</a></h2>
<p class="hero-carousel__description">Millions of volumes, 24/7 study areas, and research support for students and faculty.</p>
<div class="hero-carousel__actions">
<a href="#hours" class="btn btn--primary">Hours & Access</a>
<a href="#collections" class="btn btn--secondary">Explore Collections</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-right" role="group" aria-roledescription="slide" aria-label="Slide 2 of 2">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?w=1280&h=549&fit=crop" alt="Speaker at a presentation">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Events</span>
<h2 class="hero-carousel__title"><a href="#">Symposium on Machine Learning and Society</a></h2>
<p class="hero-carousel__description">Experts discuss the ethical and social implications of large-scale AI systems.</p>
<div class="hero-carousel__actions">
<a href="#register" class="btn btn--primary">Register</a>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="hero-carousel__prev" aria-label="Previous slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Prev</span>
</button>
<button type="button" class="hero-carousel__next" aria-label="Next slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Next</span>
</button>
<div class="hero-carousel__indicators">
<div class="hero-carousel__indicators-inner">
<button type="button" class="hero-carousel__play-pause" aria-label="Pause carousel" title="Pause">
<span class="hero-carousel__play-pause-progress" aria-hidden="true"></span>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--pause" viewBox="0 0 10 12" aria-hidden="true"><rect width="3" height="12" x="0" rx="1"/><rect width="3" height="12" x="7" rx="1"/></svg>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--play" viewBox="0 0 8 10" aria-hidden="true"><path d="M0 0v10l8-5z"/></svg>
</button>
<div role="tablist" class="hero-carousel__pips" aria-label="Carousel slides">
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="true" aria-current="true" aria-label="Slide 1" data-index="0"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 2" data-index="1"><span></span></button>
</div>
</div>
</div>
</div>
</section>
<!-- Demo 3: White overlay, right align, single slide (no carousel UI) -->
<section class="demo-section" aria-labelledby="demo3-title">
<div id="demo3-title" class="container">
<h2>Single Hero</h2>
<p>One slide: no indicators, no auto-play.</p>
</div>
<div class="hero-carousel hero-carousel--overlay-white" id="hero3" data-slide-count="1" aria-roledescription="carousel">
<div class="hero-carousel__track">
<div class="hero-carousel__slide hero-carousel__slide--align-right" role="group" aria-roledescription="slide" aria-label="Slide 1 of 1">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/2014_Columbia_University_Morningside_Heights_campus_from_southwest_.jpg/1280px-2014_Columbia_University_Morningside_Heights_campus_from_southwest_.jpg" alt="Columbia University Morningside Heights campus from southwest">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Global</span>
<h2 class="hero-carousel__title"><a href="#">Spring '25 Events Recap</a></h2>
<p class="hero-carousel__description">The Committee on Global Thought held a series of events on global economics and youth advocacy.</p>
<div class="hero-carousel__actions">
<a href="#read-more" class="btn btn--primary">Read More</a>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="hero-carousel__prev" aria-label="Previous slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Prev</span>
</button>
<button type="button" class="hero-carousel__next" aria-label="Next slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Next</span>
</button>
</div>
</section>
<!-- Demo 4: 15 slides -->
<section class="demo-section" aria-labelledby="demo4-title">
<div id="demo4-title" class="container">
<h2>Multi-Image Carousel</h2>
</div>
<div class="hero-carousel hero-carousel--overlay-black autoplay" id="hero4" data-autoplay="5000" aria-roledescription="carousel">
<div class="hero-carousel__track">
<div class="hero-carousel__slide hero-carousel__slide--align-left" role="group" aria-roledescription="slide" aria-label="Slide 1 of 15">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/View_of_Columbia_University.jpg/1280px-View_of_Columbia_University.jpg" alt="View of Columbia University campus">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Campus</span>
<h2 class="hero-carousel__title"><a href="#">Slide 1: Campus Life</a></h2>
<p class="hero-carousel__description">Explore the heart of campus and student life at Morningside Heights.</p>
<div class="hero-carousel__actions">
<a href="#" class="btn btn--primary">Learn More</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-center" role="group" aria-roledescription="slide" aria-label="Slide 2 of 15">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/2014_Columbia_University_Low_Memorial_Library_from_front.jpg/1280px-2014_Columbia_University_Low_Memorial_Library_from_front.jpg" alt="Low Memorial Library">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Research</span>
<h2 class="hero-carousel__title"><a href="#">Slide 2: Research Excellence</a></h2>
<p class="hero-carousel__description">Discover groundbreaking research and academic programs.</p>
<div class="hero-carousel__actions">
<a href="#" class="btn btn--primary">Explore</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-right" role="group" aria-roledescription="slide" aria-label="Slide 3 of 15">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Columbia_University_%285678520550%29.jpg/1280px-Columbia_University_%285678520550%29.jpg" alt="Columbia University with Butler Library">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Admissions</span>
<h2 class="hero-carousel__title"><a href="#">Slide 3: Apply Now</a></h2>
<p class="hero-carousel__description">Applications open for graduate and professional programs.</p>
<div class="hero-carousel__actions">
<a href="#" class="btn btn--primary">Apply</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-left" role="group" aria-roledescription="slide" aria-label="Slide 4 of 15">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/2014_Columbia_University_Morningside_Heights_campus_from_southwest_.jpg/1280px-2014_Columbia_University_Morningside_Heights_campus_from_southwest_.jpg" alt="Morningside Heights campus">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Libraries</span>
<h2 class="hero-carousel__title"><a href="#">Slide 4: Libraries & Collections</a></h2>
<p class="hero-carousel__description">Access millions of volumes and 24/7 study spaces.</p>
<div class="hero-carousel__actions">
<a href="#" class="btn btn--primary">Visit</a>
</div>
</div>
</div>
</div>
<div class="hero-carousel__slide hero-carousel__slide--align-center" role="group" aria-roledescription="slide" aria-label="Slide 5 of 15">
<div class="hero-carousel__image-wrap">
<img class="hero-carousel__image" src="https://images.unsplash.com/photo-1481627834876-b7833e8f5570?w=1280&h=549&fit=crop" alt="Library interior">
</div>
<div class="hero-carousel__overlay">
<div class="hero-carousel__overlay-inner">
<span class="hero-carousel__category">Events</span>
<h2 class="hero-carousel__title"><a href="#">Slide 5: Upcoming Events</a></h2>
<p class="hero-carousel__description">Lectures, symposia, and community gatherings.</p>
<div class="hero-carousel__actions">
<a href="#" class="btn btn--primary">View Calendar</a>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="hero-carousel__prev" aria-label="Previous slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Prev</span>
</button>
<button type="button" class="hero-carousel__next" aria-label="Next slide">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="hero-carousel__nav-label">Next</span>
</button>
<div class="hero-carousel__indicators">
<div class="hero-carousel__indicators-inner">
<button type="button" class="hero-carousel__play-pause" aria-label="Pause carousel" title="Pause">
<span class="hero-carousel__play-pause-progress" aria-hidden="true"></span>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--pause" viewBox="0 0 10 12" aria-hidden="true"><rect width="3" height="12" x="0" rx="1"/><rect width="3" height="12" x="7" rx="1"/></svg>
<svg class="hero-carousel__play-pause-icon hero-carousel__play-pause-icon--play" viewBox="0 0 8 10" aria-hidden="true"><path d="M0 0v10l8-5z"/></svg>
</button>
<div role="tablist" class="hero-carousel__pips" aria-label="Carousel slides">
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="true" aria-current="true" aria-label="Slide 1" data-index="0"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 2" data-index="1"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 3" data-index="2"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 4" data-index="3"><span></span></button>
<button type="button" class="hero-carousel__indicator" role="tab" aria-selected="false" aria-current="false" aria-label="Slide 5" data-index="4"><span></span></button>
</div>
</div>
</div>
</div>
</section>
<script>
(function () {
function initCarousel(container) {
var track = container.querySelector('.hero-carousel__track');
var slides = container.querySelectorAll('.hero-carousel__slide');
var indicators = container.querySelectorAll('.hero-carousel__indicator');
var playPauseBtn = container.querySelector('.hero-carousel__play-pause');
var progressEl = playPauseBtn ? playPauseBtn.querySelector('.hero-carousel__play-pause-progress') : null;
var count = slides.length;
var autoplayMs = parseInt(container.getAttribute('data-autoplay'), 10) || 6000; // 6 seconds
var hasAutoplayClass = container.classList.contains('autoplay');
var current = 0;
var autoplayTimer = null;
var autoplayStartTime = 0;
var isPaused = !hasAutoplayClass;
var dragStartX = 0;
var dragStartCurrent = 0;
var isDragging = false;
var dragStarted = false;
var activePointerId = null;
var dragThreshold = 8;
container.setAttribute('data-slide-count', count);
function getSlideDurationMs(slideIndex) {
var slide = slides[slideIndex];
if (!slide) return autoplayMs;
var type = slide.getAttribute('data-slide-type');
if (type === 'video') {
var len = parseInt(slide.getAttribute('data-video-length-ms'), 10);
if (Number.isFinite(len) && len > 0) return len;
}
return autoplayMs;
}
function buildYoutubeEmbedSrc(slide, shouldLoop) {
var videoId = slide.getAttribute('data-video-id');
var start = parseInt(slide.getAttribute('data-video-start'), 10) || 0;
var end = parseInt(slide.getAttribute('data-video-end'), 10) || 10;
var params = new URLSearchParams({
autoplay: '1',
mute: '1',
controls: '0',
playsinline: '1',
rel: '0',
modestbranding: '1',
iv_load_policy: '3',
start: String(start),
end: String(end)
});
if (shouldLoop) {
params.set('loop', '1');
// YouTube only loops reliably with playlist=VIDEO_ID.
params.set('playlist', videoId);
} else {
params.set('loop', '0');
}
return 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(videoId) + '?' + params.toString();
}
function updateVideoPlaybackForActiveSlide() {
// If autoplay is active AND multiple slides exist, video should play once then advance.
// Otherwise (paused or single slide), video should autoplay and loop while active.
var slide = slides[current];
slides.forEach(function (s, i) {
if (i === current) return;
var otherIframe = s.querySelector('.hero-carousel__video');
if (otherIframe) otherIframe.removeAttribute('src');
});
if (!slide) return;
if (slide.getAttribute('data-slide-type') !== 'video') return;
var iframe = slide.querySelector('.hero-carousel__video');
if (!iframe) return;
var shouldLoop = isPaused || count <= 1;
var provider = slide.getAttribute('data-video-provider');
var src = '';
if (provider === 'youtube') {
src = buildYoutubeEmbedSrc(slide, shouldLoop);
}
if (!src) return;
// Reset src to restart playback when slide becomes active or mode changes.
iframe.setAttribute('src', src);
}
function applyTransform(percentOffset, pixelOffset) {
var px = pixelOffset != null ? pixelOffset : 0;
track.style.transform = px === 0
? 'translateX(-' + percentOffset + '%)'
: 'translateX(calc(-' + percentOffset + '% + ' + px + 'px))';
}
function goTo(index) {
current = (index + count) % count;
container.classList.remove('is-dragging');
track.classList.remove('is-dragging');
applyTransform(current * 100, 0);
indicators.forEach(function (btn, i) {
btn.setAttribute('aria-selected', i === current ? 'true' : 'false');
btn.setAttribute('aria-current', i === current ? 'true' : null);
});
updateVideoPlaybackForActiveSlide();
resetAutoplay();
}
function next() {
goTo(current + 1);
}
function setProgress(pct) {
if (progressEl) progressEl.style.setProperty('--progress', Math.min(1, Math.max(0, pct)));
}
function resetAutoplay() {
if (autoplayTimer) clearInterval(autoplayTimer);
if (count <= 1 || !autoplayMs || isPaused) {
if (isPaused) setProgress(0);
updateVideoPlaybackForActiveSlide();
return;
}
autoplayStartTime = Date.now();
setProgress(0);
autoplayTimer = setInterval(function () {
var elapsed = Date.now() - autoplayStartTime;
var durationMs = getSlideDurationMs(current);
setProgress(elapsed / durationMs);
if (elapsed >= durationMs) {
next();
}
}, 50);
}
function updatePlayPauseLabel() {
if (!playPauseBtn) return;
if (isPaused) {
playPauseBtn.setAttribute('aria-label', 'Play carousel');
playPauseBtn.setAttribute('title', 'Play');
playPauseBtn.classList.add('is-paused');
} else {
playPauseBtn.setAttribute('aria-label', 'Pause carousel');
playPauseBtn.setAttribute('title', 'Pause');
playPauseBtn.classList.remove('is-paused');
}
}
if (playPauseBtn) {
playPauseBtn.addEventListener('click', function () {
isPaused = !isPaused;
updatePlayPauseLabel();
if (isPaused) {
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
setProgress(0);
updateVideoPlaybackForActiveSlide();
} else {
resetAutoplay();
}
});
updatePlayPauseLabel();
}
function getPointerX(e) {
if (e.clientX != null) return e.clientX;
if (e.touches && e.touches.length) return e.touches[0].clientX;
if (e.changedTouches && e.changedTouches.length) return e.changedTouches[0].clientX;
return 0;
}
function getPointerId(e) {
if (e.pointerId != null) return e.pointerId;
if (e.touches && e.touches.length) return e.touches[0].identifier;
return 0;
}
function onPointerDown(e) {
if (count <= 1) return;
if (e.target && e.target.closest && e.target.closest('a, button')) return;
var id = getPointerId(e);
if (activePointerId != null) return;
activePointerId = id;
dragStartX = getPointerX(e);
dragStartCurrent = current;
isDragging = true;
dragStarted = false;
if (container.setPointerCapture && e.pointerId != null) container.setPointerCapture(e.pointerId);
}
function onPointerMove(e) {
if (!isDragging || activePointerId !== getPointerId(e)) return;
var x = getPointerX(e);
var deltaX = x - dragStartX;
if (!dragStarted) {
if (Math.abs(deltaX) < dragThreshold) return;
dragStarted = true;
container.classList.add('is-dragging');
track.classList.add('is-dragging');
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
}
e.preventDefault();
var containerWidth = container.offsetWidth;
var resistance = 1;
if (dragStartCurrent <= 0 && deltaX > 0) resistance = 0.35;
else if (dragStartCurrent >= count - 1 && deltaX < 0) resistance = 0.35;
var dragPx = deltaX * resistance;
applyTransform(dragStartCurrent * 100, dragPx);
}
function onPointerUp(e) {
if (activePointerId !== getPointerId(e)) return;
var wasDragging = dragStarted;
activePointerId = null;
isDragging = false;
if (!wasDragging) return;
var x = getPointerX(e);
var deltaX = x - dragStartX;
var containerWidth = container.offsetWidth;
var threshold = containerWidth * 0.15;
var target = dragStartCurrent;
if (deltaX > threshold && dragStartCurrent > 0) target = dragStartCurrent - 1;
else if (deltaX < -threshold && dragStartCurrent < count - 1) target = dragStartCurrent + 1;
goTo(target);
e.preventDefault();
}
function onPointerCancel(e) {
if (activePointerId === getPointerId(e)) {
activePointerId = null;
isDragging = false;
if (dragStarted) goTo(dragStartCurrent);
container.classList.remove('is-dragging');
track.classList.remove('is-dragging');
}
}
indicators.forEach(function (btn, i) {
btn.addEventListener('click', function () {
goTo(i);
});
});
var prevBtn = container.querySelector('.hero-carousel__prev');
var nextBtn = container.querySelector('.hero-carousel__next');
if (prevBtn) prevBtn.addEventListener('click', function () { goTo(current - 1); });
if (nextBtn) nextBtn.addEventListener('click', function () { next(); });
if (count > 1) {
container.addEventListener('pointerdown', onPointerDown, { passive: false });
container.addEventListener('pointermove', onPointerMove, { passive: false });
container.addEventListener('pointerup', onPointerUp, { passive: false });
container.addEventListener('pointercancel', onPointerCancel, { passive: false });
container.addEventListener('pointerleave', function (e) {
if (e.pointerId === activePointerId && isDragging) {
onPointerUp(e);
}
}, { passive: false });
}
updateVideoPlaybackForActiveSlide();
resetAutoplay();
}
document.querySelectorAll('.hero-carousel').forEach(initCarousel);
})();
</script>
<style>
:root {
--btn-border: #FFFFFF;
--btn-primary-bg: #1D4F91;
--btn-primary-text: #FFFFFF;
--btn-secondary-bg: #53565A;
--btn-secondary-text: #FFFFFF;
--hero-overlay-white: 255, 255, 255;
--hero-overlay-white-opacity: 1;
--hero-overlay-white-text: #000000;
--hero-overlay-black: 0, 0, 0;
--hero-overlay-black-opacity: 1;
--hero-overlay-black-text: #FFFFFF;
--text-title: #003087;
--hero-indicator-border-radius: 6px;
}
@media (min-width: 768px) {
:root {
--hero-overlay-white-opacity: 0.9;
--hero-overlay-black-opacity: 0.8;
}
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
background: #FFFFFF;
color: #000000;
font-family: "Proxima Nova", -apple-system, BlinkMacSystemFont, "Segoe UI", Avenir, "Helvetica Neue", sans-serif;
margin: 0;
min-height: 100vh;
padding: 0 0 120px;
}
.container {
margin: 0 auto;
max-width: 100%;
padding: 0 15px;
}
/* ----- Hero Carousel ----- */
.hero-carousel {
margin-bottom: 60px;
overflow: hidden;
position: relative;
touch-action: pan-y pinch-zoom;
}
.hero-carousel.is-dragging {
cursor: grabbing;
}
.hero-carousel a,
.hero-carousel button {
cursor: inherit;
}
.hero-carousel__track {
display: flex;
transition: transform 0.5s ease-in-out;
will-change: transform;
}
.hero-carousel__track.is-dragging {
transition: none;
}
.hero-carousel__slide {
flex: 0 0 100%;
min-width: 0;
position: relative;
}
@media (min-width: 768px) {
.hero-carousel__slide {
max-height: 500px;
}
}
.hero-carousel__image-wrap {
aspect-ratio: 21 / 9;
background: #e8e8e8;
overflow: hidden;
position: relative;
}
/* Mobile: 16:9 aspect ratio */
@media (max-width: 767px) {
.hero-carousel__image-wrap {
aspect-ratio: 16 / 9;
}
}
.hero-carousel__image {
display: block;
height: 100%;
object-fit: cover;
width: 100%;
}
.hero-carousel__image-wrap--video {
background: #000000;
}
.hero-carousel__video {
border: 0;
height: 100%;
left: 0;
pointer-events: none;
position: absolute;
top: 0;
width: 100%;
}
/* Partial overlay: sits at bottom, does not cover full image */
.hero-carousel__overlay {
bottom: 0;
left: 0;
padding: 24px 20px 32px;
right: 0;
z-index: 1;
}
/* Overlay color: White (default) */
.hero-carousel--overlay-white .hero-carousel__overlay {
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
color: var(--hero-overlay-white-text);
}
.hero-carousel--overlay-white .hero-carousel__category,
.hero-carousel--overlay-white .hero-carousel__title,
.hero-carousel--overlay-white .hero-carousel__title a,
.hero-carousel--overlay-white .hero-carousel__description,
.hero-carousel--overlay-white .hero-carousel__link {
color: var(--hero-overlay-white-text);
}
.hero-carousel--overlay-white .hero-carousel__title a:hover {
text-decoration: underline;
}
/* Overlay color: Black */
.hero-carousel--overlay-black .hero-carousel__overlay {
background: rgba(var(--hero-overlay-black), var(--hero-overlay-black-opacity));
color: var(--hero-overlay-black-text);
}
.hero-carousel--overlay-black .hero-carousel__category,
.hero-carousel--overlay-black .hero-carousel__title,
.hero-carousel--overlay-black .hero-carousel__title a,
.hero-carousel--overlay-black .hero-carousel__description,
.hero-carousel--overlay-black .hero-carousel__link {
color: var(--hero-overlay-black-text);
}
.hero-carousel--overlay-black .hero-carousel__title a:hover {
text-decoration: underline;
}
.hero-carousel__overlay {
display: flex;
flex-direction: column;
text-align: left;
align-items: flex-start;
}
.hero-carousel__overlay-inner {
max-width: 42em;
width: 100%;
}
/* Alignment: Left (default) */
.hero-carousel__slide--align-left .hero-carousel__overlay {
text-align: left;
align-items: flex-start;
}
/* Alignment: Center */
.hero-carousel__slide--align-center .hero-carousel__overlay {
text-align: center;
align-items: center;
}
.hero-carousel__slide--align-center .hero-carousel__overlay-inner {
margin-left: auto;
margin-right: auto;
}
/* Alignment: Right */
.hero-carousel__slide--align-right .hero-carousel__overlay {
text-align: right;
align-items: flex-end;
}
.hero-carousel__slide--align-right .hero-carousel__overlay-inner {
margin-left: auto;
}
.hero-carousel__category {
display: block;
font-size: 14px;
margin-bottom: 6px;
text-transform: uppercase;
}
.hero-carousel__title {
font-size: 36px;
font-weight: 700;
line-height: 1.2;
margin: 0 0 10px 0;
}
@media (min-width: 768px) {
.hero-carousel__title {
font-size: 48px;
}
}
.hero-carousel__title a {
color: inherit;
text-decoration: none;
}
.hero-carousel__description {
font-size: 21px;
line-height: 1.4;
margin: 0 0 16px 0;
max-width: 50ch;
}
.hero-carousel__slide--align-right .hero-carousel__description {
margin-left: auto;
}
.hero-carousel__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 4px;
}
.hero-carousel__slide--align-center .hero-carousel__actions {
justify-content: center;
}
.hero-carousel__slide--align-right .hero-carousel__actions {
justify-content: flex-end;
}
.hero-carousel .btn {
border: 1px solid var(--btn-border);
border-radius: 6px;
cursor: pointer;
display: inline-block;
font-size: 18px;
line-height: 1.2;
padding: 8px 15px;
text-decoration: none;
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.hero-carousel .btn--primary {
background: var(--btn-primary-bg);
color: var(--btn-primary-text);
}
.hero-carousel .btn--secondary {
background: var(--btn-secondary-bg);
color: var(--btn-secondary-text);
font-weight: normal;
}
.hero-carousel .btn--primary:hover,
.hero-carousel .btn--primary:focus-visible {
background: var(--btn-primary-text);
border-color: var(--btn-primary-bg);
color: var(--btn-primary-bg);
}
.hero-carousel .btn--secondary:hover,
.hero-carousel .btn--secondary:focus-visible {
background: var(--btn-secondary-text);
border-color: var(--btn-secondary-bg);
color: var(--btn-secondary-bg);
}
.hero-carousel .btn:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.hero-carousel--overlay-black .btn--primary {
border-color: var(--btn-primary-bg);
}
.hero-carousel--overlay-black .btn--secondary {
background: transparent;
}
/* Prev/Next: left and right, vertically centered; hidden on mobile */
.hero-carousel__prev,
.hero-carousel__next {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
appearance: none;
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
border: none;
border-radius: var(--hero-indicator-border-radius);
color: #000000;
cursor: pointer;
padding: 10px 12px;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
transition: background 0.2s;
}
.hero-carousel__prev:hover,
.hero-carousel__next:hover {
background: rgba(var(--hero-overlay-black), var(--hero-overlay-black-opacity));
color: var(--hero-overlay-black-text);
}
.hero-carousel__prev {
left: 12px;
}
.hero-carousel__next {
right: 12px;
}
.hero-carousel__prev svg,
.hero-carousel__next svg {
width: 20px;
height: 16px;
fill: currentColor;
}
.hero-carousel__prev .hero-carousel__nav-label,
.hero-carousel__next .hero-carousel__nav-label {
font-size: 14px;
line-height: 1;
}
.hero-carousel--overlay-black .hero-carousel__prev,
.hero-carousel--overlay-black .hero-carousel__next {
background: rgba(var(--hero-overlay-black), var(--hero-overlay-black-opacity));
color: var(--hero-overlay-black-text);
}
.hero-carousel--overlay-black .hero-carousel__prev:hover,
.hero-carousel--overlay-black .hero-carousel__next:hover {
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
color: var(--hero-overlay-white-text);
}
.hero-carousel[data-slide-count="1"] .hero-carousel__prev,
.hero-carousel[data-slide-count="1"] .hero-carousel__next {
display: none;
}
@media (max-width: 767px) {
.hero-carousel__prev,
.hero-carousel__next {
display: none;
}
}
/* Indicators: centered at bottom of hero, over the overlay */
.hero-carousel__indicators {
align-items: center;
border-radius: var(--hero-indicator-border-radius);
display: flex;
justify-content: center;
z-index: 2;
max-width: 100%;
}
.hero-carousel__indicators-inner {
display: flex;
gap: 15px;
overflow-x: auto;
padding: 15px;
white-space: nowrap;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
/* Add a pseudo-element to the end of the indicators to center align pips while offsetting the play/pause button */
.hero-carousel__indicators-inner:after {
content: ' ';
flex-shrink: 1; /* allows the pseudo-element to shrink when indicators exceed viewport width */
flex-grow: 0;
margin-left: -15px; /* removes extra gap when indicators exceed viewport width */
width: 51px; /* width of the play/pause button plus the gap */
}
@media (max-width: 767px) {
.hero-carousel__indicators-inner > * {
outline: 2px solid #000000;
}
.hero-carousel__indicators-inner > *:focus {
outline: revert;
}
}
.hero-carousel__play-pause,
.hero-carousel__pips {
border-radius: var(--hero-indicator-border-radius);
flex-shrink: 0;
flex-grow: 0;
}
.hero-carousel__play-pause {
appearance: none;
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
border: none;
color: inherit;
cursor: pointer;
font: inherit;
height: 36px;
margin: 0;
padding: 0;
width: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.hero-carousel__play-pause:hover {
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
}
.hero-carousel__pips {
padding: 5px 10px;
}
.hero-carousel--overlay-white .hero-carousel__play-pause,
.hero-carousel--overlay-white .hero-carousel__pips {
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
}
.hero-carousel--overlay-black .hero-carousel__play-pause,
.hero-carousel--overlay-black .hero-carousel__pips {
background: rgba(var(--hero-overlay-black), var(--hero-overlay-black-opacity));
}
.hero-carousel--overlay-white .hero-carousel__play-pause {
color: #000000;
}
.hero-carousel--overlay-black .hero-carousel__play-pause {
color: #FFFFFF;
}
.hero-carousel--overlay-white .hero-carousel__play-pause:hover {
background: rgba(var(--hero-overlay-black), var(--hero-overlay-black-opacity));
color: #FFFFFF;
}
.hero-carousel--overlay-black .hero-carousel__play-pause:hover {
background: rgba(var(--hero-overlay-white), var(--hero-overlay-white-opacity));
color: #000000;
}
.hero-carousel__play-pause .hero-carousel__play-pause-icon {
width: 10px;
height: 12px;
fill: #ffffff;
mix-blend-mode: difference;
}
.hero-carousel__play-pause .hero-carousel__play-pause-icon--play {
display: none;
}
.hero-carousel__play-pause.is-paused .hero-carousel__play-pause-icon--pause {
display: none;
}
.hero-carousel__play-pause.is-paused .hero-carousel__play-pause-icon--play {
display: block;
}
.hero-carousel__play-pause {
position: relative;
overflow: hidden;
}
.hero-carousel__play-pause-progress {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: calc(var(--progress, 0) * 100%);
max-width: 100%;
pointer-events: none;
transition: width 0.05s linear;
}
/* Inverted color for WCAG contrast: dark on light button, light on dark button */
.hero-carousel--overlay-white .hero-carousel__play-pause-progress {
background: #000000;
}
.hero-carousel--overlay-black .hero-carousel__play-pause-progress {
background: #ffffff;
}
.hero-carousel__play-pause .hero-carousel__play-pause-icon {
position: relative;
z-index: 1;
}
/* Hide play/pause when single slide */
.hero-carousel[data-slide-count="1"] .hero-carousel__play-pause {
display: none;
}
.hero-carousel__indicator {
appearance: none;
background-color: transparent;
border: none;
cursor: pointer;
display: inline-block;
text-decoration: none;
padding: 4px
}
.hero-carousel__indicator span {
display: block;
border: 2px solid rgba(0, 0, 0, 1);
border-radius: 50%;
cursor: pointer;
height: 18px;
margin: 0;
padding: 0;
width: 18px;
}
.hero-carousel__indicator:hover span {
background: rgba(0, 0, 0, 0.5);
}
.hero-carousel__indicator[aria-current="true"] span {
background: #FFFFFF;
}
.hero-carousel--overlay-white .hero-carousel__indicator span {
border-color: rgba(0, 0, 0, 1);
}
.hero-carousel--overlay-white .hero-carousel__indicator {
color: rgba(0, 0, 0, 1);
}
.hero-carousel--overlay-white .hero-carousel__indicator[aria-current="true"] span {
background: #000000;
}
.hero-carousel--overlay-black .hero-carousel__indicator span {
border-color: rgba(255, 255, 255, 1);
}
.hero-carousel--overlay-black .hero-carousel__indicator {
color: rgba(255, 255, 255, 1);
}
.hero-carousel--overlay-black .hero-carousel__indicator[aria-current="true"] span {
background: #FFFFFF;
}
/* Single slide: hide indicators if only one */
.hero-carousel[data-slide-count="1"] .hero-carousel__indicators {
display: none;
}
@media (min-width: 768px) {
.hero-carousel__overlay {
bottom: 67px;
left: 75px;
right: auto;
position: absolute;
}
.hero-carousel__slide--align-center .hero-carousel__overlay {
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
width: fit-content;
}
.hero-carousel__slide--align-right .hero-carousel__overlay {
left: auto;
right: 75px;
}
.hero-carousel__indicators {
bottom: 0;
left: 0;
right: 0;
margin-top: 0;
position: absolute;
}
}
/* ----- Demo styles ----- */
.page-title {
margin: 0 0 1em;
}
/* Responsive */
@media (min-width: 768px) {
.container { width: 750px; }
}
@media (min-width: 992px) {
.container { width: 970px; }
}
@media (min-width: 1200px) {
.container { width: 1170px; }
}
</style>