Skip to content

Carousel

npm package: import it from 'flootlets'
bun add flootlets
Or copy it in with the shadcn CLI: import it from '@/components/ui/…'
npx shadcn@latest add https://dyeoh.github.io/flootlets/r/carousel.json

Both need Tailwind CSS v4; see Installation for the setup.

Recommended for you
import { For } from 'solid-js';
import { placeholder, products } from './placeholder';
export default function CarouselExample() {
return (
<Carousel label="Recommended for you" itemCount={products.length} class="w-full">
<Cluster justify="between">
<strong>Recommended for you</strong>
<Cluster gap={2}>
<Carousel.Previous />
<Carousel.Next />
</Cluster>
</Cluster>
<Carousel.Content>
<For each={products}>
{(p, i) => (
<Carousel.Item index={i()}>
<ProductCard>
<ProductCard.Image urls={placeholder(p.name, p.hue)} width={400} height={400} />
<ProductCard.Title href={`#${p.slug}`}>{p.name}</ProductCard.Title>
<ProductCard.Price
amount={{ amount: p.price, currency: 'MYR' }}
locale="en-MY"
size="sm"
/>
</ProductCard>
</Carousel.Item>
)}
</For>
</Carousel.Content>
<Carousel.Dots />
</Carousel>
);
}

Built on the browser’s own scroll-snap, so swiping, trackpads and Shift-scroll all work. It shows 1, 2, 3 or 4 items per screen as space allows, sized so that every page ends on a whole item. Previous and Next move a whole screen, and the dots show the screens that actually exist.

For keyboard and screen-reader users:

  • the carousel is a named region announced as a carousel, and each item is announced as “3 of 6”;
  • the track is focusable, so the arrow keys scroll it;
  • the dots have 24px tap targets and mark the current one with aria-current;
  • people who prefer reduced motion get instant jumps instead of smooth scrolling.

The paging logic comes from qilin-marketplace, where its bugs were found and fixed in real browsers, including on iOS Safari. Tests in flootlets reproduce each of those bugs, so they can’t quietly come back.

Place the parts anywhere inside <Carousel>: the buttons above the track, the dots below, or however your design needs.

Part
<Carousel label itemCount labels?> The region. label names it; itemCount is how many items it holds
<Carousel.Content> The scrolling track
<Carousel.Item index> One item; index from <For>
<Carousel.Previous> / <Carousel.Next> Buttons; disabled at the ends but still visible
<Carousel.Dots> Page dots; hidden when everything fits on one screen

labels translates the text: { previous, next, pages, page: (n) => …, slide: (i, total) => … }. To change how many items fit per screen, set --per-page on <Carousel.Content>, at each breakpoint the default uses: class="[--per-page:2] sm:[--per-page:3] min-[56rem]:[--per-page:4] min-[72rem]:[--per-page:6]". --gap sets the space between items.

flootlets 0.2.3 · Changelog