Components / Display & Media / Carousel
Carousel beta
A row of panels that scrolls like it means it — snap points, real buttons, no autoplay.
<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
Aspect Ratio beta
A box that holds its shape — 16/9 for video, 4/3 for photos, 1/1 for avatars — whatever fills it.
Avatar beta
Initials first, photo when there is one. Three sizes, a status dot, and a stacking group.
Bar Chart beta
Columns for comparing a handful of values — the chart people read correctly at a glance.
Card beta
The universal container: a hairline box with a header, a body and a footer that agree with each other.