---
title: "ProductGallery"
description: "A main image with four-column thumbnails. It can own its index or receive it controlled from the page."
kind: block
id: C-051
category: storefront
registry: "product-gallery"
docs: "https://koten.codehive.com.co/blocks/storefront/product-gallery"
markdown: "https://koten.codehive.com.co/md/blocks/storefront/product-gallery.md"
install: "npx shadcn@latest add https://koten.codehive.com.co/r/product-gallery.json"
language: en
---

# ProductGallery · ギャラリー

> Block C-051 in the Koten UI catalog.

A main image with four-column thumbnails. It can own its index or receive it controlled from the page.

> Japanese labels in this page are documentation metadata. Keep the consumer UI in the product's language and do not add Japanese decoration unless the user asks (R-031).

## Install

Install `koten-theme` first when the project does not have Koten tokens yet. Use the runner of your project.

```bash
# Node
npx shadcn@latest add https://koten.codehive.com.co/r/product-gallery.json

# Bun
bunx shadcn@latest add https://koten.codehive.com.co/r/product-gallery.json
```

## Files

- `@/components/patterns/product-gallery.tsx`
- `@/lib/koten/cn.ts`
- `@/lib/koten/format.ts`
- `@/lib/koten/intl.ts`
- `@/lib/koten/labels.ts`
- `@/lib/koten/provider.tsx`

## Requirements

- **controlled** — Controlled selection with activeIndex: Pass activeIndex with onIndexChange when the page decides the active image, for example to sync the URL or a zoom panel. Without activeIndex, the gallery uses defaultIndex and manages its own state.

## Composition

This block composes Koten primitives. Read the source files above before changing its anatomy.

## Examples

### Initial index

Without external control, defaultIndex picks the opening image and thumbnails update the internal state.

```tsx
<ProductGallery
  images={images}
  label="HIKARI K-1 gallery"
  defaultIndex={1}
  thumbLabel={(position, alt) => position + 1 + ". " + alt}
/>
```

### Controlled selection

The page stores the index and shares it with the gallery; an external button can advance the image too.

```tsx
const [index, setIndex] = useState(0);

<ProductGallery
  images={images}
  label="HIKARI K-1 gallery"
  activeIndex={index}
  onIndexChange={setIndex}
  thumbLabel={(position, alt) => position + 1 + ". " + alt}
/>

<button type="button" onClick={() => setIndex((current) => (current + 1) % images.length)}>
  Next image
</button>
```

## Props

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `images` | `readonly ProductGalleryImage[]` | `—` | Each image carries src and alt; id, width and height are optional. |
| `label` | `string` | `—` | Legend for the thumbnail fieldset, exposed to screen readers only. |
| `thumbLabel` | `(index: number, alt: string) => string` | `—` | Builds the accessible name of each thumbnail. |
| `activeIndex` | `number` | `undefined` | Enables controlled mode; it is clamped to the image range. |
| `defaultIndex` | `number` | `0` | Initial index in uncontrolled mode. |
| `onIndexChange` | `(index: number) => void` | `—` | Called when a thumbnail is pressed, even in controlled mode. |

## Accessibility

- Thumbnails are buttons with aria-pressed, grouped in a fieldset with a hidden legend.
- The main image has descriptive alt text; thumbnails use empty alt because the button already has a name.
- Each button shows a visible focus ring and activates with Enter or Space.
- Changing the image does not move focus; state is conveyed by the name and aria-pressed.

## Sources

- Catalog page: https://koten.codehive.com.co/blocks/storefront/product-gallery
- Markdown: https://koten.codehive.com.co/md/blocks/storefront/product-gallery.md
- Repository: https://github.com/codehive-colombia/koten
