Guides
Slides
How many slides sit in the viewport is a line of CSS — including three at a time, mixed widths and the snap count that follows.
How many slides are visible
Nothing about the number of visible slides passes through the hook. The slide's flex-basis decides it, the carousel measures the result and the snap positions follow.
/* one slide at a time */
.carousel__slide { flex: 0 0 100%; }
/* three slides in the viewport */
.carousel__slide { flex: 0 0 calc(100% / 3); }
/* three slides and a peek of the fourth */
.carousel__slide { flex: 0 0 calc(100% / 3.4); }
/* a fixed width, however many fit */
.carousel__slide { flex: 0 0 280px; }The 0 in flex: 0 0 … matters as much as the basis: without flex-shrink: 0 the slides are squeezed to fit the viewport and every width you set is ignored.
Three slides in the viewport
Three at a time is calc(100% / 3) on the slide — or basis-1/3 in Tailwind — with the gap paid for by the slide's padding and the track's negative margin. Here is the whole carousel, arrows included.
import { useSwipiCarousel } from '@midstem/swipi-react'
export const Carousel = ({ items }) => {
const [carouselRef, carousel] = useSwipiCarousel()
return (
<>
<div className="overflow-hidden touch-pan-y" ref={carouselRef}>
<div className="flex -ml-4 cursor-grab select-none active:cursor-grabbing">
{items.map((item) => (
<div className="min-w-0 shrink-0 grow-0 basis-1/3 pl-4" key={item.id}>
{item.title}
</div>
))}
</div>
</div>
<button
type="button"
className="z-10 bg-transparent border-none cursor-pointer disabled:opacity-[0.35] disabled:cursor-default"
onClick={carousel.scrollPrev}
disabled={!carousel.canScrollPrev}
>
‹
</button>
<button
type="button"
className="z-10 bg-transparent border-none cursor-pointer disabled:opacity-[0.35] disabled:cursor-default"
onClick={carousel.scrollNext}
disabled={!carousel.canScrollNext}
>
›
</button>
</>
)
}Slides of different widths
Because the widths are measured rather than declared, they do not have to agree with each other. A wider hero slide among narrower ones needs no option — it is a CSS selector, and the snap positions land where the slides actually start.
.carousel__slide {
box-sizing: border-box;
flex: 0 0 calc(100% / 3);
min-width: 0;
padding-left: 16px;
}
/* every third slide is twice as wide */
.carousel__slide:nth-child(3n) {
flex-basis: calc(100% / 1.5);
}Slides, snaps and overflow
The number of snap positions follows from the measurements: the carousel stops once the remaining slides fit the viewport, so five half-width slides give four snaps rather than five. That is why dots are rendered from snapCount and not from the length of your array.
const [carouselRef, carousel] = useSwipiCarousel() carousel.slidesCount // 5 — children found in the track carousel.snapCount // 4 — positions the carousel stops at carousel.hasOverflow // true — there is more than fits
slidesCount— how many children the track has.snapCount— how many positions the carousel rests at; the source for dots.hasOverflow— whether dragging does anything at all. Hide the arrows and dots behind it and a short list stops looking like a carousel.
