Thumbnail Gallery
A gallery whose main image and thumbnail strip stay in sync, supporting drag to change images, arrow keys, slide/fade transitions, and a thumbnail strip on any of four sides.
npx shadcn@latest add https://webberui.com/r/thumbnail-gallery.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ThumbnailGallery />
Installation
npx shadcn@latest add https://webberui.com/r/thumbnail-gallery.jsonOr, once registries are configured in components.json, install it as @webberui/thumbnail-gallery.
Usage
import { ThumbnailGallery } from "@/components/ui/thumbnail-gallery";
const images = [
{ src: "/photos/1.jpg", alt: "Sunset by the sea" },
{ src: "/photos/2.jpg", alt: "Forest trail", thumbnail: "/photos/2-thumb.jpg" },
{ src: "/photos/3.jpg", alt: "City at night" },
];
<ThumbnailGallery images={images} />;Controlled mode
Pass value and onValueChange to hand the current index over to outside state:
const [index, setIndex] = React.useState(0);
<ThumbnailGallery images={images} value={index} onValueChange={setIndex} />;Thumbnail position and transition effect
<ThumbnailGallery images={images} thumbnailPosition="left" effect="fade" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
images | GalleryImage[] | — | The list of images; the first item is the initial main image |
value | number | — | Controlled current index |
defaultValue | number | 0 | Initial index in uncontrolled mode |
onValueChange | (index: number) => void | — | Fires when the current index changes |
thumbnailPosition | "bottom" | "top" | "left" | "right" | "bottom" | Position of the thumbnail strip (left/right make it a vertical strip) |
effect | "slide" | "fade" | "slide" | Main image transition effect |
loop | boolean | false | Whether to wrap around at the start and the end |
showArrows | boolean | true | Whether to show the direction arrows on the main image |
aspectRatio | string | "4 / 3" | Main image aspect ratio (a CSS aspect-ratio value) |
dragThreshold | number | 60 | Drag offset threshold that changes the image (px) |
velocityThreshold | number | 400 | Drag flick velocity threshold that changes the image (px/s) |
label | string | "圖片畫廊" | Overall accessibility label — the built-in default is Traditional Chinese for "image gallery", so pass this prop to localize it |
GalleryImage
| Field | Type | Description |
|---|---|---|
src | string | Source URL of the main image |
alt | string | Alternative text (required for accessibility) |
thumbnail | string | undefined | Thumbnail source; falls back to src when omitted |
How it works
- The main image slides in (
slide) or fades in (fade) throughAnimatePresencedepending on the direction of the change, keeping the previous and next images overlapping through the transition. - Press and drag on the main image (horizontally with a horizontal thumbnail strip, vertically with a vertical one), and crossing the offset or flick velocity threshold moves to the next or previous image; below the threshold it springs back.
- The current thumbnail scrolls itself into the visible range of the thumbnail strip and centers, with the scrolling happening only inside the thumbnail strip container and never moving the whole page.
- With
loopon, the start and the end join up; with it off, the direction arrow at whichever end you have reached is disabled automatically.
Accessibility
- The main image area is a focusable carousel; the arrow keys switch to the previous / next image, and
Home/Endjump to the first / last. - The thumbnail strip is a
tablistwith each thumbnail atab, the current item marked witharia-selectedandaria-current, and only the current item Tab focusable (roving tabindex). - A built-in
aria-live="polite"region announces "image X of Y" plus the image's alternative text on each change. - When the user has "reduce motion" enabled at the system level, it switches instantly (no sliding, no fading) and the drag is disabled along with it.
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.
Stacked Peek Carousel
A card stack carousel where the layers behind peek out, and the next card moves up to take the front on each swipe.