Hover Expand Gallery — Free HTML CSS JS Accordion Gallery

Hover Expand Gallery · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Flex-grow layout
Panels share the row — no pixel widths.
Smooth rebalance
Transitioned flex-grow expands and contracts.
Caption reveal
Label fades in with a slight delay.
Readability overlay
Bottom gradient keeps text legible.
One-class logic
A single is-open class drives everything.
Hover and tap
Works on pointers and touchscreens.
Fully responsive
Adapts to any container width.
Any panel count
Flex math handles however many you add.

About this UI Snippet

Hover Expand Gallery — A Horizontal Flex Accordion

Screenshot of the Hover Expand Gallery snippet rendered live

The hover expand gallery is the horizontal image accordion where hovering a slim panel makes it grow to fill most of the row while its neighbours shrink, revealing a caption as it opens. This snippet builds it with plain HTML, CSS flexbox transitions, and a tiny vanilla JavaScript class toggle — no widths to calculate and no library.

Flex-grow does the layout

All panels live in a flex row and share the available space through flex-grow. At rest every panel has the same grow value; the open panel is given a much larger one (flex-grow: 6 versus 1). Because the panels divide the row proportionally, raising one panel's grow automatically squeezes the others — you never set explicit pixel widths, so the gallery is fully responsive and adapts to any container size. A min-width keeps the collapsed panels wide enough to remain clickable.

Animating an intrinsic property

The expansion is animated by putting a transition on flex-grow itself with an ease-out cubic-bezier, so the panels glide between their collapsed and expanded sizes. Animating flex-grow (rather than width) is what lets the whole row rebalance smoothly in one motion: as the active panel grows, the rest contract in the same frame because they're all sharing the same flexible space.

Revealing the caption

Each panel's caption starts hidden — opacity: 0 and nudged down with translateY — and only animates in when the panel gains the is-open class, with a small delay so the text appears after the panel has begun widening. This staggering makes the label feel like it belongs to the expansion rather than popping in abruptly. A bottom gradient overlay (::after) keeps the white caption readable over any panel colour.

One class, mutually exclusive

The JavaScript is deliberately minimal: an open() function toggles a single is-open class so exactly one panel is expanded at a time, and everything else — sizing, caption reveal, overlay — is driven by CSS from that one class. This keeps the logic trivial and the animation entirely declarative.

Hover and touch

Panels expand on mouseenter for pointer devices and also on click, so the gallery works on touchscreens where there is no hover — a tap opens a panel just as a hover does on desktop. This dual binding is the simple, reliable way to make a hover effect usable everywhere.

Customizing it

Swap the gradient backgrounds for real images via background-image, change the open grow ratio for a more or less dramatic expansion, adjust the timing, or add more panels — the flex math handles any count. Pair it with an image accordion, a photo gallery, or a scroll snap gallery.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out the flexbox math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why transitioning flex-grow instead of width lets every panel rebalance in one smooth motion, or why the caption's opacity transition has a small delay baked into its transition property rather than firing at the same instant the panel starts expanding. The same assistant is useful for optimizing it — ask whether toggling a class on every panel in the array on each hover (as the open function does) is more or less efficient than only touching the two panels whose state actually changed. It's just as handy for extending the gallery: ask it to auto-cycle through panels on a timer when the user isn't interacting, load real images with lazy loading instead of gradient placeholders, or make the expansion ratio configurable per panel instead of a single shared value. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a horizontal hover-expand image accordion in plain HTML, CSS, and JavaScript using flexbox — no explicit pixel widths, no library.

Requirements:
- A row of flex panels, each given the same flex-grow value at rest and a min-width so collapsed panels stay wide enough to remain clickable and hoverable.
- Give every panel a CSS transition on the flex-grow property itself (not on width) with an ease-out timing curve, so that when one panel's flex-grow is increased, every other panel in the row visibly and smoothly shrinks in the same motion as the row rebalances.
- Exactly one panel should carry an "open" state class at any time, which raises its flex-grow to a much larger value than the resting panels (creating a dominant expanded panel among slim collapsed ones).
- Each panel must contain a caption that starts fully transparent and shifted downward by a few pixels, and only transitions to fully visible and its resting position when that panel carries the open class, with the caption's own transition delayed slightly so it visibly follows after the panel begins expanding rather than appearing instantly.
- Add a semi-transparent dark gradient overlay along the bottom of every panel so a white caption text stays legible regardless of the panel's background color or image.
- Wire both a mouseenter listener (for pointer devices) and a click listener (for touch devices where hover doesn't exist) on every panel, both calling the same function that marks that one panel open and every other panel not open.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSFive colored panels render with one open.
  2. 2
    Hover a panelIt expands and the others shrink smoothly.
  3. 3
    See the captionThe label fades in as the panel opens.
  4. 4
    Tap on mobileA tap opens a panel where hover is absent.
  5. 5
    Use real imagesSet background-image on each panel.
  6. 6
    Tune the spreadChange the open panel's flex-grow ratio.

Real-world uses

Common Use Cases

Travel & portfolio
An expressive photo gallery alternative.
Feature reveals
Pair with an image accordion.
Category pickers
Visual nav above a scroll snap gallery.
Hero sections
A striking band under a minimal hero.
Case studies
Showcase projects beside a team card.
Product lines
Expand collections near a product card.

Got questions?

Frequently Asked Questions

All panels share the row through flex-grow, so raising one panel's grow value automatically squeezes the others within the same flexible space. Transitioning flex-grow lets the whole row rebalance in one smooth motion, and because no pixel widths are set, the gallery stays fully responsive to its container.

Each caption starts at opacity 0 and nudged down with translateY. When the panel gains the is-open class, the caption transitions in with a short delay, so it appears just after the panel begins widening. That stagger makes the label feel part of the expansion rather than popping in abruptly.

Yes. Panels expand on mouseenter for pointers and also on click, so a tap opens a panel on touchscreens where hover does not exist. Binding both events is the simplest reliable way to make the hover behaviour usable on mobile and desktop alike.

The open function toggles a single is-open class so exactly one panel carries it. All sizing, the caption reveal, and the overlay are driven by CSS from that one class, which keeps the JavaScript trivial and the animation entirely declarative.

Keep the open index in state and render panels from an array, applying the is-open class where the index matches. Bind onMouseEnter and onClick to set the index. All the animation is CSS, so nothing else changes; in Tailwind use grow utilities with arbitrary flex-grow values and a transition on flex-grow.