Basic Carousel
A slide carousel with prev/next arrows, dots and a polite live region.
6 個のフレームワーク中級
A carousel that cross-fades between slides instead of sliding.
<!--
Cross-fade instead of slide. Structurally this is a stack, not a row: every
slide is absolutely positioned in the same box and only opacity changes, so
there is no track and no transform.
Two consequences worth knowing:
- the stage needs an explicit height (absolute children do not size a parent)
- the faded-out slides are still painted underneath, so they get aria-hidden
AND pointer-events: none - otherwise an invisible slide would swallow clicks
Under reduced motion the fade becomes a cut (transition: none), which is the
right answer: the user still gets the change, without the dissolve.
-->
<section class="fade-car" aria-roledescription="carousel" aria-label="Product highlights" data-fade>
<div class="fade-car__stage" aria-live="polite">
<div class="fade-car__slide fade-car__slide--a is-active" role="group" aria-roledescription="slide" aria-label="1 of 3">
<h3 class="fade-car__title">Analytics</h3>
<p class="fade-car__copy">Every metric that matters, updated the moment it changes.</p>
</div>
<div class="fade-car__slide fade-car__slide--b" role="group" aria-roledescription="slide" aria-label="2 of 3" aria-hidden="true">
<h3 class="fade-car__title">Automations</h3>
<p class="fade-car__copy">Turn a repeated click into a rule that runs itself.</p>
</div>
<div class="fade-car__slide fade-car__slide--c" role="group" aria-roledescription="slide" aria-label="3 of 3" aria-hidden="true">
<h3 class="fade-car__title">Integrations</h3>
<p class="fade-car__copy">Forty connectors, and a webhook for everything else.</p>
</div>
<button class="fade-car__arrow fade-car__arrow--prev" type="button" aria-label="Previous slide" data-fade-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="fade-car__arrow fade-car__arrow--next" type="button" aria-label="Next slide" data-fade-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 class="fade-car__dots" data-fade-dots>
<button class="fade-car__dot" type="button" aria-label="Go to slide 1" aria-current="true"></button>
<button class="fade-car__dot" type="button" aria-label="Go to slide 2"></button>
<button class="fade-car__dot" type="button" aria-label="Go to slide 3"></button>
</div>
</section>
<script>
(function () {
document.querySelectorAll('[data-fade]').forEach(function (root) {
var slides = Array.prototype.slice.call(root.querySelectorAll('.fade-car__slide'));
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-fade-dots] button'));
var index = 0;
function go(next) {
index = (next + slides.length) % slides.length;
slides.forEach(function (slide, i) {
slide.classList.toggle('is-active', i === index);
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');
});
}
root.querySelector('[data-fade-prev]').addEventListener('click', function () {
go(index - 1);
});
root.querySelector('[data-fade-next]').addEventListener('click', function () {
go(index + 1);
});
dots.forEach(function (dot, i) {
dot.addEventListener('click', function () {
go(i);
});
});
});
})();
</script>| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
items必須 | Slide[] | - | レンダリングする項目の配列。 |
onSelect | (index: number) => void | - | ユーザーが選択したメニュー項目を引数に呼ばれます。 |
className | string | - | ルート要素にマージされる追加クラス。 |
Structurally a stack, not a row: every slide is `absolute inset-0` in the same box and only opacity changes, so there is no track and no transform. Two consequences follow. The stage needs an explicit height, because absolutely positioned children cannot size their parent - change `h-56` to fit your tallest slide. And the faded-out slides are still painted underneath, so they get `aria-hidden` *and* `pointer-events-none`; without the latter an invisible slide silently swallows clicks meant for the one on top. Under reduced motion the dissolve becomes a cut, which is the right answer: the change still happens, without the motion.