Hover Expand Gallery
A horizontally squeezing expand gallery — panels change their share of the space on hover or keyboard focus, while the rest collapse and dim.
npx shadcn@latest add https://webberui.com/r/hover-expand-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.
<HoverExpandGallery />
Installation
npx shadcn@latest add https://webberui.com/r/hover-expand-gallery.jsonOr, once registries are configured in components.json, install it as @webberui/hover-expand-gallery.
Usage
import {
HoverExpandGallery,
HoverExpandGalleryItem,
} from "@/components/ui/hover-expand-gallery";
<HoverExpandGallery className="h-64 w-full max-w-md" defaultActiveIndex={0}>
<HoverExpandGalleryItem label="Ridge" description="The ridgeline at daybreak">
<img src="/mountain.jpg" alt="" className="h-full w-full object-cover" />
</HoverExpandGalleryItem>
<HoverExpandGalleryItem label="Bay" description="Sandbars after the tide goes out">
<img src="/bay.jpg" alt="" className="h-full w-full object-cover" />
</HoverExpandGalleryItem>
</HoverExpandGallery>;Each panel's share of the space is driven by its flex-grow weight: the expanded panel grows while the rest collapse proportionally. The container needs a height (horizontal orientation) or a width (vertical), and the panels fill it automatically.
Props
HoverExpandGallery
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Put HoverExpandGalleryItem children here |
activeIndex | number | null | — | Controlled expanded index; null means every panel collapses to an equal share |
defaultActiveIndex | number | null | 0 | Initial expanded index in uncontrolled mode; set null for equal shares by default |
onActiveIndexChange | (index: number | null) => void | — | Fires when the expanded index is committed (hover does not fire it) |
orientation | "horizontal" | "vertical" | "horizontal" | Layout direction: horizontal or vertical squeeze |
expandOnHover | boolean | true | Whether to expand on cursor hover |
expandedFlex | number | 5 | flex-grow weight of the expanded panel |
collapsedFlex | number | 1 | flex-grow weight of a collapsed panel |
gap | number | 8 | Gap between panels (px) |
minItemSize | number | 44 | Minimum size of a collapsed panel (px) |
aria-label | string | "互動圖庫" | Accessible name for the gallery group (the built-in default is Traditional Chinese — pass this prop to name it in your own language) |
HoverExpandGalleryItem
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Full-bleed background visual for the panel (image, gradient, and so on) |
label | ReactNode | — | Title revealed on expand, which also serves as the accessible name |
description | ReactNode | — | Subtitle revealed on expand |
icon | ReactNode | — | Corner identifier icon that stays visible when collapsed |
How it works
- The expanded state is decided by two layers, "hovered" and "committed (active)": hovering temporarily expands the hovered panel, and leaving returns to the active panel.
- Clicking or keyboard-focusing a panel commits it as active, so when browsing with the keyboard the focused panel is the expanded one.
- When
activeIndexis out of range or set tonull, every panel is laid out atcollapsedFlexin equal shares. - An expanded panel reveals a gradient mask and title along its bottom edge, while collapsed panels get a translucent dark layer to sharpen the focus.
Accessibility
- Each panel is a native
<button>carryingaria-expandedto reflect its expanded state, withlabel(ordescription) as its accessible name. - The container is
role="group"with anaria-label, overridable through the prop of the same name. - Keyboard:
Tabmoves focus panel by panel, arrow keys (←/→ when horizontal, ↑/↓ when vertical) move focus between panels,Home/Endjump to the first and last;Enter/Spacecommits the expansion. - When the user has "reduce motion" enabled at the system level, panel sizes and reveals switch instantly with no transition animation.
Tilt Card
A 3D perspective tilt card — the card tilts with the cursor, a glare sweeps across its surface, it springs back on leave, and content can float up with translateZ.
Bento Grid
A bento-box grid layout — cards reveal with a stagger, and on hover the focused card lifts while the rest dim slightly.