Basic Carousel
A slide carousel with prev/next arrows, dots and a polite live region.
6 फ्रेमवर्कमध्यम
A carousel that advances on a timer, pauses on hover and focus, and respects reduced motion.
<!--
Autoplay is the version that gets accessibility wrong most often. Four rules,
all implemented below:
1. WCAG 2.2.2: anything that moves for more than 5s needs a pause control.
There is a real play/pause button, and its label flips with the state.
2. It pauses on hover AND on focus-within - a keyboard user tabbing to the next
arrow must not have the slide move out from under them.
3. prefers-reduced-motion: reduce => it never starts, and the transition is
removed. Not "slower": off.
4. aria-live is "off" while playing (an announcement every 5s is noise) and
flips to "polite" the moment it pauses, so manual moves are still announced.
-->
<section class="ap-carousel" aria-roledescription="carousel" aria-label="Product highlights" data-ap-carousel>
<div class="ap-carousel__viewport" aria-live="off" data-ap-live>
<div class="ap-carousel__track" data-ap-track>
<div class="ap-carousel__slide ap-carousel__slide--a" role="group" aria-roledescription="slide" aria-label="1 of 3">
<h3 class="ap-carousel__title">Analytics</h3>
<p class="ap-carousel__copy">Every metric that matters, updated the moment it changes.</p>
</div>
<div class="ap-carousel__slide ap-carousel__slide--b" role="group" aria-roledescription="slide" aria-label="2 of 3" aria-hidden="true">
<h3 class="ap-carousel__title">Automations</h3>
<p class="ap-carousel__copy">Turn a repeated click into a rule that runs itself.</p>
</div>
<div class="ap-carousel__slide ap-carousel__slide--c" role="group" aria-roledescription="slide" aria-label="3 of 3" aria-hidden="true">
<h3 class="ap-carousel__title">Integrations</h3>
<p class="ap-carousel__copy">Forty connectors, and a webhook for everything else.</p>
</div>
</div>
</div>
<div class="ap-carousel__bar">
<button class="ap-carousel__toggle" type="button" aria-label="Pause automatic slide show" data-ap-toggle>
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-ap-icon-pause>
<path d="M6.5 4h2.5v12H6.5zM11 4h2.5v12H11z" />
</svg>
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-ap-icon-play hidden>
<path d="M6.5 4l9 6-9 6z" />
</svg>
</button>
<div class="ap-carousel__dots" data-ap-dots>
<button class="ap-carousel__dot" type="button" aria-label="Go to slide 1" aria-current="true"></button>
<button class="ap-carousel__dot" type="button" aria-label="Go to slide 2"></button>
<button class="ap-carousel__dot" type="button" aria-label="Go to slide 3"></button>
</div>
<div class="ap-carousel__arrows">
<button class="ap-carousel__arrow" type="button" aria-label="Previous slide" data-ap-prev>
<svg 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="ap-carousel__arrow" type="button" aria-label="Next slide" data-ap-next>
<svg 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>
</div>
</section>
<script>
(function () {
var INTERVAL = 5000;
document.querySelectorAll('[data-ap-carousel]').forEach(function (root) {
var track = root.querySelector('[data-ap-track]');
var live = root.querySelector('[data-ap-live]');
var slides = Array.prototype.slice.call(track.children);
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-ap-dots] button'));
var toggle = root.querySelector('[data-ap-toggle]');
var iconPause = root.querySelector('[data-ap-icon-pause]');
var iconPlay = root.querySelector('[data-ap-icon-play]');
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var index = 0;
var playing = !reduced; // never auto-start under reduced motion
var hovered = false;
var focused = false;
var timer = null;
function render() {
track.style.transform = 'translateX(' + index * -100 + '%)';
slides.forEach(function (slide, i) {
if (i === index) slide.removeAttribute('aria-hidden');
else slide.setAttribute('aria-hidden', 'true');
});
dots.forEach(function (dot, i) {
if (i === index) dot.setAttribute('aria-current', 'true');
else dot.removeAttribute('aria-current');
});
}
function running() {
return playing && !hovered && !focused && !reduced;
}
function sync() {
if (timer) {
clearInterval(timer);
timer = null;
}
if (running()) {
timer = setInterval(function () {
index = (index + 1) % slides.length;
render();
}, INTERVAL);
}
// Silent while it moves on its own; announces once it stops.
live.setAttribute('aria-live', running() ? 'off' : 'polite');
iconPause.hidden = !playing;
iconPlay.hidden = playing;
toggle.setAttribute(
'aria-label',
playing ? 'Pause automatic slide show' : 'Start automatic slide show'
);
}
function go(next) {
index = (next + slides.length) % slides.length;
render();
}
if (reduced) track.style.transition = 'none';
root.addEventListener('mouseenter', function () {
hovered = true;
sync();
});
root.addEventListener('mouseleave', function () {
hovered = false;
sync();
});
root.addEventListener('focusin', function () {
focused = true;
sync();
});
root.addEventListener('focusout', function () {
focused = false;
sync();
});
toggle.addEventListener('click', function () {
playing = !playing;
sync();
});
root.querySelector('[data-ap-prev]').addEventListener('click', function () {
go(index - 1);
});
root.querySelector('[data-ap-next]').addEventListener('click', function () {
go(index + 1);
});
dots.forEach(function (dot, i) {
dot.addEventListener('click', function () {
go(i);
});
});
render();
sync();
});
})();
</script>| Prop | टाइप | डिफ़ॉल्ट | विवरण |
|---|---|---|---|
itemsआवश्यक | Slide[] | - | रेंडर की जाने वाली आइटम की सूची। |
onSelect | (index: number) => void | - | यूज़र द्वारा चुने गए मेन्यू आइटम के साथ चलता है। |
className | string | - | रूट एलिमेंट पर मर्ज होने वाली अतिरिक्त क्लासेज़। |
Four rules make autoplay acceptable, and all four are in here. There is a real pause button (WCAG 2.2.2: anything moving for more than five seconds needs one). It pauses on hover *and* on focus-within, so a keyboard user tabbing to the arrows does not have the slide move out from under them. Under `prefers-reduced-motion` it never starts - off, not slower - and the preference is watched live, because users flip it mid-session. And `aria-live` is `off` while it plays, flipping to `polite` the moment it stops: a carousel that announces itself every five seconds is noise, but a manual move still deserves an announcement. Change `INTERVAL` freely; do not remove the pause control.