Horizontally scrollable card container with navigation arrows and an optional action link.
overflow-x: auto and hidden scrollbars. Items have a fixed width and flex-shrink: 0 to prevent collapse. On desktop, the container is sized so that 3 full items are visible plus a peek (~15%) of the next item, signaling more content. Navigation arrows in the top-right of the header row scroll the container by 80% of its visible width using scrollTo({ behavior: 'smooth' }). Arrows disable at the scroll edges (opacity drops to 0.3). An optional “View All” action link sits to the right of the arrows. On mobile (<768px), arrows are hidden and native touch scrolling takes over. On tablet (768–1023px), 2 items plus a peek are visible.
3 items visible + peek on desktop, 2 + peek on tablet, 1 + peek on mobile. Navigation arrows in header row.
4 items visible + peek. For smaller cards (images, thumbnails). Same scroll mechanics.
| Name | Type | Default | Description |
|---|---|---|---|
title | string | — | Section heading text |
subtitle | string | — | Section subheading text |
items | array | [] | Carousel items (content defined by consumer) |
actionLabel | string | — | Text for the “View All” link (omit to hide) |
actionHref | string | # | Destination for the action link |
scrollAmount | number | 0.8 | Fraction of container width to scroll per click (0–1) |
className | string | "" | Additional CSS classes on the outer container |
// Carousel controller — vanilla JS
// Attach to any element with [data-carousel]
function initCarousel(el) {
const track = el.querySelector('[data-carousel-track]');
const prev = el.querySelector('[data-carousel-prev]');
const next = el.querySelector('[data-carousel-next]');
if (!track || !prev || !next) return;
const scrollAmount = 0.8; // 80% of visible width
function checkEdges() {
const { scrollLeft, scrollWidth, clientWidth } = track;
prev.disabled = scrollLeft < 10;
next.disabled = scrollLeft + clientWidth >= scrollWidth - 10;
}
prev.addEventListener('click', () => {
track.scrollTo({
left: track.scrollLeft - track.clientWidth * scrollAmount,
behavior: 'smooth'
});
});
next.addEventListener('click', () => {
track.scrollTo({
left: track.scrollLeft + track.clientWidth * scrollAmount,
behavior: 'smooth'
});
});
track.addEventListener('scroll', checkEdges);
window.addEventListener('resize', checkEdges);
checkEdges();
}
SparksCarousel (OG-017 reference)
▾
<SparksCarousel
title="Featured Items"
subtitle="A collection of things"
actionLabel="View All"
actionHref="/items"
items={[
{ id: 1, imageSrc: '/img/1.jpg', title: 'Item One', subtitle: 'Cat A' },
{ id: 2, imageSrc: '/img/2.jpg', title: 'Item Two', subtitle: 'Cat B' },
]}
/>