Cover Carousel
A horizontal carousel of cover cards whose indicator is driven continuously (scrubbed) by the scroll ratio, with full keyboard navigation and ARIA.
npx shadcn@latest add https://webberui.com/r/cover-carousel.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<CoverCarousel />
Installation
npx shadcn@latest add https://webberui.com/r/cover-carousel.jsonOr, once registries are configured in components.json, install it as @webberui/cover-carousel.
Usage
Wrap everything in CoverCarousel and put a number of CoverCarouselItems inside; the width of each slide is up to you via className:
import {
CoverCarousel,
CoverCarouselItem,
} from "@/components/ui/cover-carousel";
<CoverCarousel ariaLabel="Featured models">
{items.map((item) => (
<CoverCarouselItem key={item.id} className="w-[240px]">
<Card {...item} />
</CoverCarouselItem>
))}
</CoverCarousel>;Controlled mode: pass index and onIndexChange to drive the current slide from outside.
const [index, setIndex] = React.useState(0);
<CoverCarousel index={index} onIndexChange={setIndex}>
{/* ... */}
</CoverCarousel>;Props
CoverCarousel
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Put CoverCarouselItems here, one per slide |
index | number | — | Current index in controlled mode; changing it scrolls to that slide |
defaultIndex | number | 0 | Initial index in uncontrolled mode |
onIndexChange | (index: number) => void | — | Fires when the centered slide changes |
controls | boolean | true | Whether to show the left and right arrow buttons |
indicators | "dots" | "bar" | "none" | "dots" | Style of the indicator at the bottom |
loop | boolean | false | Whether to wrap around at the start and the end |
snapAlign | "start" | "center" | "start" | The scroll-snap alignment point of the slides |
gap | number | 16 | Spacing between slides (px) |
ariaLabel | string | "輪播" | Accessible label of the carousel region — the built-in default is Traditional Chinese for "carousel", so pass this prop to localize it |
className | string | — | The outer region |
viewportClassName | string | — | The horizontal scroll viewport |
CoverCarouselItem
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Slide content |
aria-label | string | automatic | Overrides this slide's accessible label; defaults to "slide N of M" |
className | string | — | Sets the slide's width and appearance |
How it works
- Scroll scrub: the indicator (the dot pill or the progress bar) is driven continuously by the scroll container's horizontal scroll ratio
scrollXProgressand smoothed withuseSpring, so while dragging between two slides the pill slides continuously rather than jumping between discrete positions. - Alignment detection: scroll events are throttled with
requestAnimationFrame, and the slide closest to the viewport's center line is measured as the current index, while the at-start / at-end state is updated at the same time to disable the arrows. - Two modes: without
indexit is uncontrolled (tracking the scroll position internally); passindexand it becomes controlled, so an outside change scrolls to that slide.
Accessibility
- The whole thing is
role="region"witharia-roledescription="輪播"(Traditional Chinese for "carousel"), and each slide isrole="group"labeled "slide N of M". - The scroll viewport is focusable, and once focused
←→(or↑↓) switch slides whileHome/Endjump to the first and the last. - The arrows and the dots are all native buttons that can be focused and triggered by keyboard; the dots use
role="tab"witharia-selectedto mark the current slide. - The current slide number is announced through a hidden region with
aria-live="polite". - When the user turns on "reduce motion", programmatic scrolling positions instantly and the indicator applies no spring smoothing.
Swipe Cards
A Tinder-style swipe card stack that can be dragged, flies out by velocity, and offers programmatic left/right swipes and an empty state.
ClipPath Carousel
A carousel with clip-path reveal transitions in four built-in styles — wipe, iris, diagonal, and box — supporting drag, autoplay, and controlled and uncontrolled modes.