Basic Carousel
A slide carousel with prev/next arrows, dots and a polite live region.
6 frameworksIntermediate
A full-bleed hero carousel with gradient panels, headings and real CTAs.
<!-- A full-bleed hero carousel: each slide is a gradient panel with an eyebrow,
heading, copy and a real CTA link. Overlaid arrows and dots keep contrast
with a translucent chip behind them. Slides page with a transform. -->
<section class="relative overflow-hidden rounded-2xl" aria-roledescription="carousel" aria-label="Featured" data-fh>
<div class="flex transition-transform duration-500 ease-out motion-reduce:transition-none" aria-live="polite" data-fh-track>
<div class="flex min-h-[20rem] shrink-0 basis-full flex-col justify-center bg-gradient-to-br from-blue-600 to-indigo-700 px-6 py-14 text-white sm:px-12" role="group" aria-roledescription="slide" aria-label="1 of 2">
<p class="text-xs font-semibold uppercase tracking-wide text-white/80">New</p>
<h2 class="mt-2 max-w-lg text-3xl font-bold sm:text-4xl">Ship faster with one shared workspace</h2>
<p class="mt-3 max-w-md text-sm leading-relaxed text-white/90">Docs, tasks and dashboards where your team already works.</p>
<a class="mt-6 inline-flex w-fit items-center rounded-lg bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-blue-700" href="#">Get started</a>
</div>
<div class="flex min-h-[20rem] shrink-0 basis-full flex-col justify-center bg-gradient-to-br from-teal-700 to-emerald-700 px-6 py-14 text-white sm:px-12" role="group" aria-roledescription="slide" aria-label="2 of 2" aria-hidden="true">
<p class="text-xs font-semibold uppercase tracking-wide text-white/80">Enterprise</p>
<h2 class="mt-2 max-w-lg text-3xl font-bold sm:text-4xl">Security that clears the audit</h2>
<p class="mt-3 max-w-md text-sm leading-relaxed text-white/90">SSO, SCIM and an immutable log, ready on day one.</p>
<a class="mt-6 inline-flex w-fit items-center rounded-lg bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-700" href="#">Talk to sales</a>
</div>
</div>
<button class="absolute left-3 top-1/2 grid h-10 w-10 -translate-y-1/2 place-items-center rounded-full bg-black/30 text-white backdrop-blur hover:bg-black/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white" type="button" aria-label="Previous slide" data-fh-prev>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m12.5 5-5 5 5 5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<button class="absolute right-3 top-1/2 grid h-10 w-10 -translate-y-1/2 place-items-center rounded-full bg-black/30 text-white backdrop-blur hover:bg-black/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white" type="button" aria-label="Next slide" data-fh-next>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m7.5 5 5 5-5 5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<div class="absolute inset-x-0 bottom-4 flex justify-center gap-2" data-fh-dots>
<button class="h-2 w-2 rounded-full bg-white/50 hover:bg-white/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white aria-[current=true]:w-5 aria-[current=true]:bg-white" type="button" aria-label="Go to slide 1" aria-current="true"></button>
<button class="h-2 w-2 rounded-full bg-white/50 hover:bg-white/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white aria-[current=true]:w-5 aria-[current=true]:bg-white" type="button" aria-label="Go to slide 2"></button>
</div>
</section>
<script>
(function () {
document.querySelectorAll('[data-fh]').forEach(function (root) {
var track = root.querySelector('[data-fh-track]');
var slides = Array.prototype.slice.call(track.children);
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-fh-dots] button'));
var index = 0;
function go(next) {
index = (next + slides.length) % slides.length;
track.style.transform = 'translateX(' + index * -100 + '%)';
slides.forEach(function (s, i) { if (i === index) s.removeAttribute('aria-hidden'); else s.setAttribute('aria-hidden', 'true'); });
dots.forEach(function (d, i) { if (i === index) d.setAttribute('aria-current', 'true'); else d.removeAttribute('aria-current'); });
}
root.querySelector('[data-fh-prev]').addEventListener('click', function () { go(index - 1); });
root.querySelector('[data-fh-next]').addEventListener('click', function () { go(index + 1); });
dots.forEach(function (d, i) { d.addEventListener('click', function () { go(i); }); });
});
})();
</script>| Prop | Type | Default | Description |
|---|---|---|---|
itemsrequired | HeroSlide[] | - | The array of items to render. |
className | string | - | Additional classes merged onto the root element. |
ariaLabel | string | 'Featured' | The button's accessible name. Required for icon-only buttons. |
Each slide is a gradient panel with an eyebrow, heading, copy and a genuine CTA link; off-screen CTAs drop out of the tab order so focus never lands on a hidden slide. Overlaid arrows and dots sit on translucent chips for contrast against any gradient.