# React product image gallery

_Box Kite 2.2.4 · a markdown copy of https://www.box-kite.dev/blocks/react-product-image-gallery/_

A React product image gallery: one large view and thumbnails that choose it, built as tabs so the arrow keys move between pictures.

## Install

One command copies the files into your project, as code you then own. It needs a `components.json`, and [the shadcn registry page](https://www.box-kite.dev/registry.md) has the one-time setup.

```shell
npx shadcn@latest add https://www.box-kite.dev/r/product-gallery.json
```

[Open in v0](https://v0.dev/chat/api/open?url=https%3A%2F%2Fwww.box-kite.dev%2Fr%2Fproduct-gallery.json)

[The item JSON](https://www.box-kite.dev/r/product-gallery.json)

1 file · installs @box-kite/react, store-design

## Light and dark

The same component twice, each half in a theme of its own — nothing in it is written for either. [Open it full screen](https://www.box-kite.dev/blocks/react-product-image-gallery/demo.md) to see it in yours.

## Source

The files the command writes, exactly as the preview above runs them. Every style is a prop, so there is no stylesheet to copy with them.

**box-kite/store/product-gallery.tsx**

```jsx
'use client';
import Tabs from '@box-kite/react/components/tabs';
import VisuallyHidden from '@box-kite/react/components/visuallyHidden';
import ProductArt, { type ArtView } from './product-art';
import { finishes, type Product, productBySlug } from './store-data';

const views: { value: ArtView; label: string }[] = [
  { value: 'front', label: 'Front' },
  { value: 'set', label: 'All finishes' },
];

const list = new Intl.ListFormat('en');

export interface ProductGalleryProps {
  product?: Product;
  /** The finish on show — the variant picker beside the gallery owns it. */
  finish?: string;
}

/**
 * A product's pictures: one large view and a row of thumbnails that choose it. The thumbnails are tabs
 * (APG's tabbed carousel), so the arrow keys move between them and each picture is named by its tab.
 */
export default function ProductGallery({ product = productBySlug('table-lamp'), finish = product.finishes[0] }: ProductGalleryProps) {
  const also = product.finishes.filter((id) => id !== finish);
  const labels: Record<ArtView, string> = {
    front: `${product.name} in ${finishes[finish].name}`,
    set: `${product.name} in ${list.format([finish, ...also].map((id) => finishes[id].name))}`,
  };

  return (
    <Tabs defaultValue="front" gap={3}>
      <Tabs.List label={`${product.name} pictures`} order={1} bb={0} gap={3}>
        {views.map((view) => (
          <Tabs.Tab
            key={view.value}
            value={view.value}
            px={1}
            py={1}
            bgColor="transparent"
            mb={0}
            width={20}
            height={20}
            borderRadius={3}
            b={1}
            borderColor="transparent"
            hover={{ borderColor: 'stone-300' }}
            ariaAttr={{ selected: { borderColor: 'stone-900' } }}
            focusVisible={{ outlineOffset: 2 }}
            theme={{
              dark: { bgColor: 'transparent', hover: { borderColor: 'stone-600' }, ariaAttr: { selected: { borderColor: 'stone-100' } } },
            }}
          >
            <ProductArt shape={product.shape} finish={finish} also={also} view={view.value} stroke={1.5} radius={2} />
            <VisuallyHidden>{view.label}</VisuallyHidden>
          </Tabs.Tab>
        ))}
      </Tabs.List>
      {views.map((view) => (
        <Tabs.Panel key={view.value} value={view.value} aspectRatio="square" borderRadius={6} startingStyle={{ opacity: 0 }}>
          <ProductArt
            shape={product.shape}
            finish={finish}
            also={also}
            view={view.value}
            label={labels[view.value]}
            stroke={0.5}
            radius={6}
          />
        </Tabs.Panel>
      ))}
    </Tabs>
  );
}
```

It stands on `store-design`, which the command installs with it. The design the store blocks and pages share: the components restyled with Box.createComponents(), the catalogue, the drawn product art, and the header and footer. [The design is on the template page.](https://www.box-kite.dev/templates/react-product-page.md#design)

## Used in

- [React product page template](https://www.box-kite.dev/templates/react-product-page.md)

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

