Guides
Responsive
Changing the number of visible slides per breakpoint, in Tailwind and in plain CSS, and the one change the observer cannot see.
Breakpoints are CSS
There is no breakpoints option, because there is nothing for it to do. The number of visible slides is a flex-basis, so a media query changes it, and a ResizeObserver notices the new measurement and recalculates the snap positions without a render. That is true in every framework — the CSS below is the whole mechanism.
.carousel__slide {
box-sizing: border-box;
flex: 0 0 calc(100% / 3);
min-width: 0;
padding-left: 24px;
}
@media (max-width: 800px) {
.carousel__slide {
flex-basis: calc(100% / 2);
}
}
@media (max-width: 580px) {
.carousel__slide {
flex-basis: 100%;
}
}The whole carousel
Three slides above 800px, two below it and one below 580px, with a tighter gap on the smallest screens. The Tailwind tab writes the same thing as max-[800px]:basis-1/2 variants.
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-6 max-[580px]:-ml-3 cursor-grab select-none active:cursor-grabbing">
{items.map((item) => (
<div className="min-w-0 shrink-0 grow-0 basis-1/3 pl-6 max-[800px]:basis-1/2 max-[580px]:basis-full max-[580px]:pl-3" 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>
<nav className="relative flex items-center gap-2.5">
{Array.from({ length: carousel.snapCount }, (_, index) => (
<button
type="button"
className="h-3 w-3 p-0 bg-[#d2d2d2] border-none rounded-full cursor-pointer data-[active=true]:bg-[#b70808]"
key={index}
data-active={index === carousel.selectedIndex}
onClick={() => carousel.scrollTo(index)}
/>
))}
</nav>
</>
)
}Container queries
Nothing here is tied to the viewport width. If the carousel lives in a sidebar that changes width on its own, a container query is the more honest breakpoint — and it is measured the same way.
.carousel {
container-type: inline-size;
}
@container (max-width: 640px) {
.carousel__slide {
flex-basis: 100%;
}
}The one change that goes unnoticed
Change it through spaceBetween instead, reading --swipi-slide-gap in your CSS as described in Spacing & sizing. A breakpoint that changes the gap and the number of visible slides is fine either way — the width changes with it.
const [carouselRef] = useSwipiCarousel({
spaceBetween: isMobile ? 12 : 24
})