Image Row
Creating an Interactive Image Row with Hover Expansion and Touch Support in React and TypeScript
This guide walks you through how to build a responsive and interactive horizontal image gallery that expands on hover (or remains expanded by default, on mobile) using React, TypeScript, and CSS Modules.
The component provides a smooth and intuitive user experience by dynamically resizing images when hovered, revealing titles and an overlaid play button. It adapts automatically to both desktop and touch devices, ensuring consistent behavior across different input types.
Technologies Used
- React (with hooks like
useEffect,useState,useRef) - TypeScript for type safety and better development experience
- CSS Modules for scoped, modular styling
- Next.js Link for client-side navigation



Building an Expandable Image Row Component
The ImageRow component creates a horizontally aligned gallery of images that expand smoothly when hovered (desktop) or tapped (mobile). Each image shows a title and a play button overlay, enhancing interactivity while keeping the layout clean and elegant.
Let's break down how it works and how to build it step-by-step
1. Component Structure and Image Data: The component imports a small array of image objects, each containing:
- An
id(unique identifier), - An image
src, - A
title, - And a
videoUrl(link destination).
These objects define the visual and interactive content of each image container.
1const images = [
2 { id: 1, src: "/images/monalisa.jpg", title: "Monalisa", videoUrl: "/" },
3 { id: 2, src: "/images/monalisa.jpg", title: "Monalisa", videoUrl: "/" },
4 ...
5];2. Managing State and Device Type: The component manages two main pieces of state:
activeId— the ID of the currently expanded image.isTouchDevice— a boolean that detects if the user is on a touch-enabled device.
The useEffect hook runs once on mount to check for touch capabilities using the presence of the ontouchstart event or navigator.maxTouchPoints.
1useEffect(() => {
2 if (typeof window !== "undefined") {
3 setIsTouchDevice("ontouchstart" in window || navigator.maxTouchPoints > 0);
4 }
5}, []);This ensures hover logic is disabled on touch devices, avoiding conflicts with mobile gestures.
3. Hover and Touch Behavior: Each image container listens for onMouseEnter events. When hovered, the activeId is updated, causing that image to expand while others collapse.
On touch devices, the behavior is adjusted so all images are slightly expanded, allowing taps to interact naturally without requiring hover.
1onMouseEnter={() => !isTouchDevice && setActiveId(img.id)}This logic guarantees consistent responsiveness across input types.
4. Dynamic Styling and Class Management: The layout and animation effects are handled through CSS Modules, where conditional classes control transitions and visibility.
Each image container uses either:
styles.expanded→ when active or touchedstyles.collapsed→ when inactive
Similarly, the play button and title visibility are toggled using conditional classnames like styles.showPlayButton and styles.showTitle.
1<div
2 className={`${styles.imageContainer} ${img.id === activeId ? styles.expanded : styles.collapsed}`}
3>The CSS transitions (e.g. width, opacity, transform) create a smooth animation between the expanded and collapsed states.
5. Overlay and Interactive Elements: Inside each container:
- The image is wrapped in a div (
imageWrapper) that controls scaling and clipping. - The play button is implemented as a
<Link>component overlaying the image, containing an inline SVG triangle icon. - The title appears below or over the image when the image is active or on touch devices.
1<Link
2 href={img.videoUrl}
3 className={`${styles.playButton} ${
4 img.id === activeId || isTouchDevice ? styles.showPlayButton : ""
5 }`}
6>
7 <svg ...><path d="M8 5v14l11-7z" /></svg>
8</Link>This structure keeps the markup semantic and accessible while maintaining flexibility for design variations.
6. Responsive and Scalable Layout: The CSS ensures that all images share equal height, align horizontally, and adjust to viewport size. When an image is expanded, it grows proportionally while the others shrink, preserving the overall layout.
Typical CSS rules include:
display: flexon the row containertransition: width 0.4sease on each image containeroverflow: hiddento prevent content overflow- Responsive adjustments for smaller screens (e.g., reducing height or padding)
7. Touch Device Adaptation: Instead of hover interactions, touch devices rely on a default expanded mode or a simpler tap-to-navigate experience. The isTouchDevice flag ensures that:
- All images remain slightly expanded.
- The play buttons and titles are always visible for easier tap access.
- Hover-only animations are disabled.
This avoids hover-based logic that doesn't translate well to mobile.
8. Performance and Cleanup: Because this component relies only on lightweight state updates and CSS transitions, it performs efficiently even with several images. No scroll listeners or heavy DOM observers are used, and the logic runs only on hover or touch events, making it highly optimized.
Final Thoughts
This ImageRow component demonstrates how to combine React hooks, responsive design, and modular CSS animations to create an interactive, visually appealing user experience.
By intelligently detecting input type (mouse or touch) and applying state-based transitions, the component remains fluid and accessible on any device.
This approach can be easily extended — for instance, you could:
- Add video previews on hover.
- Integrate lazy loading for images.
- Add keyboard navigation for accessibility.
It's a clean, scalable pattern that strikes a balance between simplicity, interactivity, and performance — perfect for modern React projects.