GitHub

Components / Display & Media / Carousel

Carousel beta

A row of panels that scrolls like it means it — snap points, real buttons, no autoplay.

Preview

Cast from one mold

Every part shares tokens, heights and motion, so a page assembled from strangers still reads as one product.

Zero dependencies

No framework, no build step, no runtime. Paste the part, link the tokens, ship it.

The core stays vermilion

One signature, used once per state: the center of whatever is on. Nothing else wears it.

<div class="k-car" style="max-width:430px">
  <div class="k-car-track" data-car-track>
    <div class="k-car-slide"><h4>Cast from one mold</h4><p>Every part shares tokens, heights and motion, so a page assembled from strangers still reads as one product.</p></div><div class="k-car-slide"><h4>Zero dependencies</h4><p>No framework, no build step, no runtime. Paste the part, link the tokens, ship it.</p></div><div class="k-car-slide"><h4>The core stays vermilion</h4><p>One signature, used once per state: the center of whatever is on. Nothing else wears it.</p></div>
  </div>
  <div class="k-car-ui">
    <button class="k-car-btn" type="button" data-car-prev aria-label="Previous slide"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 12H4m6-6-6 6 6 6"/></svg></button>
    <button class="k-car-btn" type="button" data-car-next aria-label="Next slide"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12h16m-6-6 6 6-6 6"/></svg></button>
    <div class="k-car-dots"><button type="button" data-car-dot aria-label="Go to slide 1" aria-current="true"></button><button type="button" data-car-dot aria-label="Go to slide 2" aria-current="false"></button><button type="button" data-car-dot aria-label="Go to slide 3" aria-current="false"></button></div>
  </div>
</div>
.k-car{font-family:var(--k-font);text-align:left}
.k-car-track{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;padding-bottom:4px}
.k-car-track::-webkit-scrollbar{display:none}
.k-car-slide{flex:0 0 78%;scroll-snap-align:start;border:1px solid var(--k-line);border-radius:14px;background:var(--k-bg);padding:18px;min-height:118px}
.k-car-slide h4{font-size:14px;font-weight:600;color:var(--k-ink)}
.k-car-slide p{margin-top:5px;font-size:12.5px;line-height:1.7;color:var(--k-ink-2)}
.k-car-ui{display:flex;align-items:center;gap:10px;margin-top:12px}
.k-car-btn{width:32px;height:32px;border-radius:50%;border:1px solid var(--k-line);background:var(--k-bg);color:var(--k-ink);display:grid;place-items:center;cursor:pointer}
.k-car-btn:hover{background:var(--k-bg-soft)}
.k-car-dots{display:flex;gap:6px;margin-left:auto}
.k-car-dots button{width:7px;height:7px;border-radius:99px;border:none;background:var(--k-line);cursor:pointer;padding:0;transition:width .18s var(--k-ease),background .18s}
.k-car-dots button[aria-current="true"]{width:20px;background:var(--k-ink)}
const carTrack = document.querySelector('[data-car-track]');
const carSlides = [...carTrack.children];
const carDots = [...document.querySelectorAll('[data-car-dot]')];
function carGo(i) {
  carTrack.scrollTo({ left: carSlides[i].offsetLeft - carTrack.offsetLeft, behavior: 'smooth' });
}
function carIndex() {
  const x = carTrack.scrollLeft + 8;
  let best = 0;
  carSlides.forEach((s, i) => { if (s.offsetLeft - carTrack.offsetLeft <= x) best = i; });
  return best;
}
document.querySelector('[data-car-prev]').addEventListener('click', () => carGo(Math.max(0, carIndex() - 1)));
document.querySelector('[data-car-next]').addEventListener('click', () => carGo(Math.min(carSlides.length - 1, carIndex() + 1)));
carDots.forEach((d, i) => d.addEventListener('click', () => carGo(i)));
carTrack.addEventListener('scroll', () => {
  const i = carIndex();
  carDots.forEach((d, j) => d.setAttribute('aria-current', j === i ? 'true' : 'false'));
}, { passive: true });

Usage

For galleries and card rails on narrow screens. On wide screens it is a grid that happens to scroll; design for that first.

Accessibility

Built on native scroll, so touch, trackpads and keyboards all work. Buttons move by one panel; dots are buttons too, labelled by position.

Related parts