Product Image Carousel
A headless, flexible product image carousel component for e-commerce storefronts. Supports image navigation, thumbnail display, and variant-specific images.
Purpose
This component provides a headless carousel solution for displaying product images in e-commerce storefronts. It handles image loading, navigation, thumbnail management, and variant-specific image filtering. The component is designed to be fully customizable through its slot-based architecture and render prop pattern, allowing complete control over styling and layout while providing essential carousel functionality out of the box.
Features
- Image carousel with navigation
- Thumbnail navigation and display
- Variant-specific image filtering
- Render prop pattern for custom layouts
- Accessibility support with ARIA attributes
- Loading state management
- Active slide tracking and control
Installation
- npm
- Yarn
npm install @haus-storefront-react/product-image-carousel
yarn add @haus-storefront-react/product-image-carousel
Note: This is not a public package. Contact the Haus Tech Team for access.
API Reference
ProductImageCarousel.Root
Root component for ProductImageCarousel. Provides context to all child components. Can be used with either regular children or a render prop function. Requires either an id or slug prop to identify the product.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes* | - | Product ID (required if slug is not provided) |
slug | string | Yes* | - | Product slug (required if id is not provided) |
hideThumbsIfSingle | boolean | No | true | Hide thumbnails if only one image |
variantImagesOnly | boolean | No | false | Show only variant-specific images |
preloadedAssetSrc | string | No | - | Preloaded featured image URL shown before product assets resolve |
children | ReactNode | (context) => ReactNode | No | - | Either React nodes or render prop with carousel context |
* Either id or slug must be provided.
ProductImageCarousel.Wrapper
Wrapper component for the entire carousel. Must be used within ProductImageCarousel.Root. Provides accessibility attributes.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element instead of div |
ProductImageCarousel.Carousel
Carousel component for the main carousel container. Must be used within ProductImageCarousel.Wrapper. Automatically applies data-single-image attribute when only one image.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element instead of div |
ProductImageCarousel.InnerWrapper
Inner wrapper component for the main image. Must be used within ProductImageCarousel.Carousel.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element instead of div |
ProductImageCarousel.SelectedImage
Selected image component for displaying the current image. Must be used within ProductImageCarousel.InnerWrapper. Returns null while loading.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
alt | string | No | Product name | Alt text for the image |
asChild | boolean | No | false | Render as child element instead of img |
ProductImageCarousel.ThumbWrapper
Thumbnails wrapper component for the thumbnail container. Must be used within ProductImageCarousel.Carousel.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element instead of div |
ProductImageCarousel.Thumbnails
Thumbnails component that renders all thumbnails. Must be used within ProductImageCarousel.ThumbWrapper. Automatically hides if only one image and hideThumbsIfSingle is true.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element instead of div |
ProductImageCarousel.Thumb
Individual thumbnail component. Must be used within ProductImageCarousel.Thumbnails.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
index | number | Yes | - | The thumbnail index |
asset | Asset | Yes | - | The asset data for the thumbnail |
asChild | boolean | No | false | Render as child element instead of button |
ProductImageCarousel.ThumbImage
Thumbnail image component. Must be used within ProductImageCarousel.Thumb.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asset | Asset | Yes | - | The asset data for the thumbnail image |
asChild | boolean | No | false | Render as child element instead of img |
useProductImageCarousel
Custom hook that provides product image carousel functionality including asset loading, slide management, and thumbnail visibility logic. Listens for product variant selection events via the event bus.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
options | ProductImageCarouselVariables | Yes | Configuration object |
Options Object
interface ProductImageCarouselVariables {
id?: string
slug?: string
hideThumbsIfSingle?: boolean
variantImagesOnly?: boolean
preloadedAssetSrc?: string
}
Note: Either id or slug must be provided.
Returns
| Return Value | Type | Description |
|---|---|---|
product | Product | undefined | The loaded product data or undefined while loading |
isLoading | boolean | Loading state flag |
assets | Asset[] | Array of product assets |
activeSlide | number | Current active slide index |
setActiveSlide | (index: number) => void | Function to set the active slide |
handleThumbClick | (index: number) => void | Function to handle thumbnail click |
shouldShowThumbnails | boolean | Whether thumbnails should be displayed |
isSingleImage | boolean | Whether there is only one image |
useProductImageCarouselProps
Utility hook that composes useProductImageCarousel and returns prebuilt prop getters for custom UI implementations (getWrapperProps, getThumbProps, etc.).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes* | Product ID (required when slug is not provided) |
slug | string | Yes* | Product slug (required when id is not provided) |
hideThumbsIfSingle | boolean | No | Hide thumbnail tray when only one image is available |
variantImagesOnly | boolean | No | Restrict assets to currently selected variant images |
preloadedAssetSrc | string | No | Temporary image source shown before product assets are available |
* Either id or slug must be provided.
Returns
Includes all values from useProductImageCarousel plus prop getter utilities:
getWrapperPropsgetCarouselPropsgetInnerWrapperPropsgetSelectedImagePropsgetThumbWrapperPropsgetThumbPropsgetThumbImagePropsgetThumbnailsProps
Basic Usage
Simple Component
- React
- React Native
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function ProductPage() {
return (
<ProductImageCarousel.Root id='product-id'>
<ProductImageCarousel.Wrapper>
<ProductImageCarousel.Carousel>
<ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.SelectedImage />
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper>
<ProductImageCarousel.Thumbnails />
</ProductImageCarousel.ThumbWrapper>
</ProductImageCarousel.Carousel>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function ProductPage() {
return (
<ProductImageCarousel.Root id='product-id'>
<ProductImageCarousel.Wrapper>
<ProductImageCarousel.Carousel>
<ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.SelectedImage />
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper>
<ProductImageCarousel.Thumbnails />
</ProductImageCarousel.ThumbWrapper>
</ProductImageCarousel.Carousel>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
Basic Hook Usage
- React
- React Native
import { useProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function MyComponent() {
const { assets, isLoading, activeSlide } = useProductImageCarousel({
id: 'product-id',
})
if (isLoading) return <div>Loading...</div>
if (!assets.length) return <div>No images</div>
return (
<div>
<img src={assets[activeSlide]?.source} alt='Product' />
</div>
)
}
import { View, Image, Text } from 'react-native'
import { useProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function MyComponent() {
const { assets, isLoading, activeSlide } = useProductImageCarousel({
id: 'product-id',
})
if (isLoading) return <Text>Loading...</Text>
if (!assets.length) return <Text>No images</Text>
return (
<View>
<Image source={{ uri: assets[activeSlide]?.source }} />
</View>
)
}
Advanced Usage
Custom Styling with asChild
- React
- React Native
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function StyledProductGallery() {
return (
<ProductImageCarousel.Root id='product-id'>
<ProductImageCarousel.Wrapper asChild>
<div className='custom-carousel-wrapper'>
<ProductImageCarousel.Carousel asChild>
<div className='custom-carousel'>
<ProductImageCarousel.InnerWrapper asChild>
<div className='custom-inner-wrapper'>
<ProductImageCarousel.SelectedImage
asChild
alt='Product image'
/>
</div>
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper asChild>
<div className='custom-thumb-wrapper'>
<ProductImageCarousel.Thumbnails />
</div>
</ProductImageCarousel.ThumbWrapper>
</div>
</ProductImageCarousel.Carousel>
</div>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
import { View } from 'react-native'
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function StyledProductGallery() {
return (
<ProductImageCarousel.Root id='product-id'>
<ProductImageCarousel.Wrapper asChild>
<View>
<ProductImageCarousel.Carousel asChild>
<View>
<ProductImageCarousel.InnerWrapper asChild>
<View>
<ProductImageCarousel.SelectedImage asChild>
<View />
</ProductImageCarousel.SelectedImage>
</View>
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper asChild>
<View>
<ProductImageCarousel.Thumbnails />
</View>
</ProductImageCarousel.ThumbWrapper>
</View>
</ProductImageCarousel.Carousel>
</View>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
Variant-Specific Images
- React
- React Native
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function VariantSpecificGallery() {
return (
<ProductImageCarousel.Root id='product-id' variantImagesOnly={true}>
<ProductImageCarousel.Wrapper>
<ProductImageCarousel.Carousel>
<ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.SelectedImage />
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper>
<ProductImageCarousel.Thumbnails />
</ProductImageCarousel.ThumbWrapper>
</ProductImageCarousel.Carousel>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
import { View } from 'react-native'
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function VariantSpecificGallery() {
return (
<ProductImageCarousel.Root id='product-id' variantImagesOnly>
<ProductImageCarousel.Wrapper asChild>
<View>
<ProductImageCarousel.Carousel asChild>
<View>
<ProductImageCarousel.InnerWrapper asChild>
<View>
<ProductImageCarousel.SelectedImage />
</View>
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper asChild>
<View>
<ProductImageCarousel.Thumbnails />
</View>
</ProductImageCarousel.ThumbWrapper>
</View>
</ProductImageCarousel.Carousel>
</View>
</ProductImageCarousel.Wrapper>
</ProductImageCarousel.Root>
)
}
Custom Thumbnail Rendering
- React
- React Native
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function CustomThumbnails() {
return (
<ProductImageCarousel.Root id='product-id'>
{({ assets, activeSlide, handleThumbClick }) => (
<ProductImageCarousel.Wrapper>
<ProductImageCarousel.Carousel>
<ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.SelectedImage />
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper>
{assets.map((asset, index) => (
<ProductImageCarousel.Thumb
key={asset.id}
index={index}
asset={asset}
className={activeSlide === index ? 'active' : ''}
>
<ProductImageCarousel.ThumbImage asset={asset} />
</ProductImageCarousel.Thumb>
))}
</ProductImageCarousel.ThumbWrapper>
</ProductImageCarousel.Carousel>
</ProductImageCarousel.Wrapper>
)}
</ProductImageCarousel.Root>
)
}
import { View, Pressable } from 'react-native'
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function CustomThumbnails() {
return (
<ProductImageCarousel.Root id='product-id'>
{({ assets, activeSlide, handleThumbClick }) => (
<ProductImageCarousel.Wrapper asChild>
<View>
<ProductImageCarousel.Carousel asChild>
<View>
<ProductImageCarousel.InnerWrapper asChild>
<View>
<ProductImageCarousel.SelectedImage />
</View>
</ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.ThumbWrapper asChild>
<View>
{assets.map((asset, index) => (
<ProductImageCarousel.Thumb
key={asset.id}
index={index}
asset={asset}
asChild
>
<Pressable onPress={() => handleThumbClick(index)}>
<ProductImageCarousel.ThumbImage asset={asset} />
</Pressable>
</ProductImageCarousel.Thumb>
))}
</View>
</ProductImageCarousel.ThumbWrapper>
</View>
</ProductImageCarousel.Carousel>
</View>
</ProductImageCarousel.Wrapper>
)}
</ProductImageCarousel.Root>
)
}
Conditional Rendering Patterns
- React
- React Native
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function ConditionalProductGallery() {
return (
<ProductImageCarousel.Root id='product-id'>
{({ assets, isLoading, shouldShowThumbnails, isSingleImage }) => {
if (isLoading) {
return <div>Loading product images...</div>
}
if (!assets.length) {
return <div>No images available</div>
}
return (
<ProductImageCarousel.Wrapper>
<ProductImageCarousel.Carousel>
<ProductImageCarousel.InnerWrapper>
<ProductImageCarousel.SelectedImage />
</ProductImageCarousel.InnerWrapper>
{shouldShowThumbnails && !isSingleImage && (
<ProductImageCarousel.ThumbWrapper>
<ProductImageCarousel.Thumbnails />
</ProductImageCarousel.ThumbWrapper>
)}
</ProductImageCarousel.Carousel>
</ProductImageCarousel.Wrapper>
)
}}
</ProductImageCarousel.Root>
)
}
import { View, Text } from 'react-native'
import { ProductImageCarousel } from '@haus-storefront-react/product-image-carousel'
function ConditionalProductGallery() {
return (
<ProductImageCarousel.Root id='product-id'>
{({ assets, isLoading, shouldShowThumbnails, isSingleImage }) => {
if (isLoading) {
return <Text>Loading product images...</Text>
}
if (!assets.length) {
return <Text>No images available</Text>
}
return (
<ProductImageCarousel.Wrapper asChild>
<View>
<ProductImageCarousel.Carousel asChild>
<View>
<ProductImageCarousel.InnerWrapper asChild>
<View>
<ProductImageCarousel.SelectedImage />
</View>
</ProductImageCarousel.InnerWrapper>
{shouldShowThumbnails && !isSingleImage && (
<ProductImageCarousel.ThumbWrapper asChild>
<View>
<ProductImageCarousel.Thumbnails />
</View>
</ProductImageCarousel.ThumbWrapper>
)}
</View>
</ProductImageCarousel.Carousel>
</View>
</ProductImageCarousel.Wrapper>
)
}}
</ProductImageCarousel.Root>
)
}
Made with ❤️ by Haus Tech Team