Sticker Carousel
A strip of stickers peeled off the roll sideways. Native scroll-snap does the scrolling; the arrows grey out at each end, the fade paints only the side that is hiding something, and neither appears while the strip fits.
NavigationClient componentlucide-react
Preview
Continue watching
Arrows grey out at each end. The fade only shows where content is hiding.
Installation
The CLI writes the source into your project and pulls in whatever it depends on.
$
pnpm dlx shadcn@latest add @duck/sticker-carouselPulls in @duck/theme. Already installed items are skipped.
Usage
import { StickerCarousel } from "@/components/ui/sticker-carousel"The file lands in components/ui/sticker-carousel.tsx and belongs to you from that point on. Edit it in place rather than wrapping it.
Props
Everything not listed here is forwarded to the underlying element.
| Prop | Type | Default | Description |
|---|---|---|---|
| title | React.ReactNode | - | Heading above the track. |
| description | React.ReactNode | - | Line under the heading. |
| actions | React.ReactNode | - | Extra content in the heading row, for example a "See all" link. |
| label | string | - | Accessible name for the track. Falls back to title when that is a string. |
| controls | "edge" | "header" | "none" | "edge" | Arrows floating over the track edges, in the heading row, or not at all. |
| gap | "sm" | "default" | "lg" | "default" | Space between slides, 8px through 24px. |
| peek | boolean | false | Pad the track so slides peek in from the edge instead of touching it. Scroll padding moves with it, so snapping still lands flush. |
| page | number | 0.85 | Share of the visible width one arrow press or key travels. Under 1 so a partly seen slide stays on screen as the anchor. |
| fade | boolean | true | Fade the edge that still has content behind it. |
Rules
What keeps this component from turning into noise.
- Direct children are the slides. The track pins them to shrink-0 and snap-start, so all a slide has to bring is a width.
- Arrow state comes from the scroll position, not from a slide index: a scroll listener plus a ResizeObserver on the track and every slide, so late-loading artwork cannot leave a dead arrow behind.
- Nothing here is arrow-only. The track is focusable, arrows and PageUp/PageDown page it, Home and End go to the ends — which is also why tabIndex has to stay on it.
- The fade is a mask on the track, so it works on any surface. It would also eat the track's own focus ring, so the ring is drawn on the viewport around it.
- RTL is read from the computed direction and scrollLeft is treated as negative-going, per the current spec. Legacy WebKit, where RTL started at scrollWidth, is not handled.
