MediaGallery
ImplementedResponsive image+video gallery grid (2/3/4 columns). Tiles become full-size buttons when onSelect is provided; video tiles show a centered play indicator. Empty state renders a role='status' message.
patterns/molecules/mediagallery.htmlResponsive image/video gallery grid (2/3/4 columns) with an optional selection mode and an announced empty state.
MediaGallery
molecules
Uniform aspect-square tile grid. Image tiles render as-is; video tiles show a centered play affordance over a scrim. With onSelect, each tile becomes a real full-size button named "${openLabel} ${alt}" — the molecule is a grid + select-handler only, preview/lightbox UX belongs to the consumer.
Click a tile — onSelect hands the item to your preview/lightbox UX (the molecule ships no lightbox).
MediaGallery (empty state)
molecules
Zero items render a dashed placeholder with role="status" aria-live="polite", so assistive tech announces it when media arrives.
No media yet
Usage
import { MediaGallery } from '@epasslive/design-system'
const media = [
{ id: 'stage', src: '/stage.jpg', alt: 'Main stage at dusk' },
{ id: 'trailer', src: '/trailer.mp4', alt: 'Event trailer', type: 'video' },
]
// Select mode — tiles are buttons; open your own preview/lightbox
<MediaGallery items={media} columns={3} onSelect={openPreview} />
// Display-only grid — tiles are inert cells
<MediaGallery items={media} columns={4} />
// Localized labels (FR)
<MediaGallery
items={media}
emptyLabel="Aucun média"
galleryLabel="Galerie médias"
openLabel="Ouvrir"
onSelect={openPreview}
/>When to use
- Event photo/video grids on public event pages (uniform media wall).
- Producer media manager — select a tile to preview, reorder, or delete.
- Anywhere a uniform grid of media thumbnails is needed — not for mixed content cards (use a card molecule instead).
Accessibility
The grid is a labelled list (role="list" with aria-label) of role="listitem" tiles. Selectable tiles are real <button type="button"> elements covering the full tile (WCAG 2.5.5 target size), named `${openLabel} ${item.alt}`, with a visible focus-visible ring. The empty state is announced via role="status" aria-live="polite". The video play affordance is decorative and aria-hidden — the button's accessible name carries the meaning. No built-in lightbox: keyboard/focus-trap UX for previews belongs to the consuming overlay component.
Props
items*MediaGalleryItem[]Gallery items — { id, src, alt, type?: "image" | "video", thumbnail? }. alt is required by contract (pass "" consciously for decorative media).
onSelect(item: MediaGalleryItem) => voidWhen provided, tiles render as real <button type="button"> elements; omit for inert display cells.
columns2 | 3 | 43Responsive column count. 3 → grid-cols-2 sm:grid-cols-3; 4 adds lg:grid-cols-4; 2 stays two-up.
emptyLabelstring"No media yet"Text shown in the empty state. Injectable for localization.
galleryLabelstring"Media gallery"Accessible name (aria-label) for the list. Injectable for localization.
openLabelstring"Open"Verb prefix for selectable tiles, rendered as `${openLabel} ${item.alt}`. Injectable for localization (e.g. "Ouvrir").
classNamestringAdditional CSS classes merged onto the root (grid or empty state).
idstringStable root id; defaults to media-gallery-<useId()>.
Props
MediaGallery
itemsMediaGalleryItem[]—Gallery items — { type: 'image' | 'video', src, alt, poster? } (MediaGalleryItem).
onSelect(item: MediaGalleryItem) => void—Tiles render as full-size buttons calling this; omit for a static (non-interactive) gallery.
columns2 | 3 | 43Grid column count.
emptyLabelstring'No media yet'Empty-state message (DS i18n keys exist: mediaGallery.empty).
galleryLabelstring'Media gallery'Accessible name for the gallery list — override for localization (e.g. 'Galerie médias').
openLabelstring'Open'Verb prefix for selectable tiles — the button name renders as `{openLabel} {item.alt}`.
classNamestring—Additional CSS classes.
idstringauto (useId)Stable root id.
<MediaGallery />Accessibility
ARIA
2 rulesUsage
Import
import { MediaGallery } from "@epasslive/design-system"Example 1
<MediaGallery items={media} onSelect={openLightbox} columns={3} />Example 2
<MediaGallery items={[]} emptyLabel="Aucun média" galleryLabel="Galerie médias" />Anti-patterns
— Do not wrap tiles in raw <img onClick> — keyboard users cannot reach them; onSelect makes them real buttons
— Do not hand-roll the empty state — emptyLabel renders the status region