<bmx-gallery>
A picture gallery with a viewer: thumbnails in a grid, in masonry columns, or in rows of equal height that fill the width (as photo libraries show them), and a full-screen viewer that zooms, pans, pinches, swipes and plays a slideshow. Videos play in the viewer, and a before/after pair is shown with a slider to compare the two.
20 properties · 7 events · 9 methods · 4 parts
Example
Open a picture to zoom (wheel, pinch, double click or + and -), swipe or use the arrows, play the slideshow or show its details. The glacier lake is a before/after pair; drag its handle.
Show markup
<bmx-gallery id="ex-gal" toolbar selectable download group-by="month" sort="date-desc" item-size="200" style="--bmx-gallery-height: 34rem"></bmx-gallery>
<p class="note" id="ex-gal-out" role="status" style="margin: 12px 0 0">Open a picture to zoom (wheel, pinch, double click or + and -), swipe or use the arrows, play the slideshow or show its details. The glacier lake is a before/after pair; drag its handle.</p>
<script type="module">
await customElements.whenDefined('bmx-gallery');
const gallery = document.getElementById('ex-gal');
const out = document.getElementById('ex-gal-out');
const base = 'assets/gallery/';
// A fictional photo club's year: each picture with its size (so the layout is right before anything loads), a date, tags and a photographer.
const photos = [
['alpine-sunset', 1500, 1000, 'Alpine sunset', '2026-09-14', 'Mara Lind', 'mountains, sunset', 'The last light over the western ridges, from the hut terrace.'],
['harbour-dawn', 1500, 1000, 'Harbour at dawn', '2026-09-02', 'Tom Okafor', 'sea, lighthouse', 'The lighthouse on the north arm, a few minutes before sunrise.'],
['dunes-noon', 1500, 940, 'Dunes at noon', '2026-08-21', 'Priya Shah', 'desert'],
['milky-way', 1000, 1400, 'The Milky Way', '2026-08-12', 'Mara Lind', 'night, stars', 'A 20-second exposure on the night of the Perseids.'],
['aurora-fjord', 1500, 1000, 'Aurora over the fjord', '2026-03-03', 'Erik Dahl', 'night, aurora'],
['city-dusk', 1500, 1000, 'City at dusk', '2026-07-30', 'Sam Rivera', 'city'],
['lantern-bokeh', 1000, 1000, 'Lanterns', '2026-07-18', 'Amira Haddad', 'abstract, night'],
['rolling-hills', 1500, 1000, 'Rolling hills', '2026-06-29', 'Priya Shah', 'hills, summer'],
['violet-ridges', 1400, 1050, 'Violet ridges', '2026-09-20', 'Erik Dahl', 'mountains'],
['silver-coast', 1500, 860, 'Silver coast', '2026-02-14', 'Tom Okafor', 'sea, winter'],
['autumn-valley', 1050, 1400, 'Autumn valley', '2026-10-04', 'Amira Haddad', 'hills, autumn'],
['evening-peaks', 1500, 640, 'Evening peaks, panorama', '2026-09-14', 'Mara Lind', 'mountains, panorama'],
['mirror-lake', 1000, 1400, 'Mirror lake', '2026-07-05', 'Sam Rivera', 'lake, reflection'],
['red-dunes', 1400, 1050, 'Red dunes', '2026-08-22', 'Priya Shah', 'desert'],
['city-night', 1000, 1400, 'City at night', '2026-07-31', 'Sam Rivera', 'city, night'],
['polar-night', 1000, 1250, 'Polar night', '2026-03-04', 'Erik Dahl', 'night, aurora'],
['garden-bokeh', 1500, 1000, 'Garden lights', '2026-06-12', 'Amira Haddad', 'abstract'],
['green-downs', 1500, 760, 'The downs', '2026-06-30', 'Priya Shah', 'hills, summer'],
['storm-ridges', 1500, 1000, 'Before the storm', '2026-10-09', 'Erik Dahl', 'mountains, weather'],
['desert-stars', 1500, 1000, 'Desert stars', '2026-08-23', 'Mara Lind', 'desert, night'],
['bay-sunset', 1500, 1000, 'Sunset over the bay', '2026-09-03', 'Tom Okafor', 'sea, sunset'],
['highland-mist', 1500, 1000, 'Highland mist', '2026-10-11', 'Erik Dahl', 'mountains, weather'],
['terracotta-hills', 1400, 1050, 'Terracotta hills', '2026-10-05', 'Amira Haddad', 'hills, autumn'],
];
gallery.items = [
...photos.map(([name, width, height, title, date, author, tags, caption]) => ({
id: name,
src: `${base}${name}.jpg`,
thumb: `${base}${name}-thumb.jpg`,
width,
height,
title,
alt: title,
caption,
date,
author,
tags: tags.split(', '),
})),
// A before/after pair: the same lake before and after its colour grade.
{
id: 'glacier-lake',
type: 'compare',
src: `${base}glacier-lake.jpg`,
before: `${base}glacier-lake-before.jpg`,
thumb: `${base}glacier-lake-thumb.jpg`,
width: 1500,
height: 1000,
title: 'Glacier lake, graded',
alt: 'A glacier lake under blue sky, mountains reflected in still water',
caption: 'Drag the handle to compare the raw file with the graded one.',
date: '2026-09-18',
author: 'Mara Lind',
tags: ['lake', 'before and after'],
meta: { Camera: 'Field 24', Lens: '35 mm', Exposure: '1/250 s, f/8' },
},
// A video plays in the viewer.
{
id: 'induction',
type: 'video',
src: 'assets/media/induction.mp4',
poster: 'assets/media/induction-poster.jpg',
width: 1280,
height: 720,
title: 'Club safety talk',
alt: 'Video: the club safety talk',
date: '2026-09-01',
author: 'Photo club',
tags: ['video'],
},
];
gallery.actions = [{ id: 'favourite', label: 'Add to favourites', icon: '♡' }];
gallery.addEventListener('bmxSelectionChange', e => (out.textContent = `bmxSelectionChange: ${e.detail.selection.length} selected.`));
gallery.addEventListener('bmxView', e => (out.textContent = `bmxView: ${e.detail.item.title} (${e.detail.index + 1}).`));
gallery.addEventListener('bmxAction', e => (out.textContent = `bmxAction: ${e.detail.action} on ${e.detail.item.title}.`));
gallery.addEventListener('bmxChange', e => (out.textContent = `bmxChange: ${e.detail.reason} - layout ${e.detail.layout}, size ${e.detail.itemSize}px, search “${e.detail.query}”.`));
</script>
<bmx-gallery id="photos" layout="justified" toolbar selectable></bmx-gallery>
<script>
photos.items = [{ src: 'harbour.jpg', thumb: 'harbour-small.jpg', width: 1600, height: 1000, alt: 'The harbour at dawn' }];
</script>
Only the thumbnails in view are in the page, worked out from each
picture's size before any of them has loaded, so ten thousand pictures
scroll as smoothly as twenty. Pictures without a size are laid out as
squares and settle into their shape as they load. Grouping by day, month,
year or the page's own groups puts headings in, one kept at the top while
its pictures scroll by. With has-more, bmxLoadMore asks the page for
the next page of items as the reader nears the end.
Accessibility: the thumbnails are a list (a listbox when selectable)
with each picture's position in the whole set; arrow keys move to the
picture really above, below or beside, Page Up and Down a screen at a time.
The viewer is a modal dialog: Escape closes it and focus returns to the
thumbnail; every control is a labelled button, the before/after handle is
a slider, and the picture shown is announced.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
actions |
actions |
readonly BmxGalleryAction[] | string |
[] |
Buttons added to the viewer's toolbar; a click fires bmxAction. |
captions |
captions |
'hover' | 'below' | 'none' |
'hover' |
Where thumbnails show their titles: over the picture on hover and focus, under it always, or not at all. |
cellAspect |
cell-aspect |
number |
1 |
A grid cell's shape, width over height. |
download |
download |
boolean |
false |
The viewer offers a download of each picture. |
fit |
fit |
'cover' | 'contain' |
'cover' |
How a picture sits in its grid cell: filling it (cropped) or wholly inside it. |
gap |
gap |
number |
8 |
The space between thumbnails, in pixels. |
groupBy |
group-by |
BmxGalleryGroupBy |
'none' |
Headings by the items' group, or by the day, month or year of their date. |
hasMore |
has-more |
boolean |
false |
More items can be loaded: bmxLoadMore fires as the reader nears the end. |
itemSize |
item-size |
number |
220 |
The size aimed for, in pixels: the row height for justified, the column width for grid and masonry. Mutable. |
items |
items |
readonly (Partial<BmxGalleryItem> | string)[] | string |
[] |
The pictures: objects (src, thumb, width, height, alt, title, caption, date, tags, type, before, poster...) or plain addresses, or JSON of either. |
label |
label |
string |
— | The gallery's accessible name. Default: "Gallery". |
layout |
layout |
BmxGalleryLayout |
'justified' |
How the thumbnails are arranged. Mutable: the toolbar changes it. |
loop |
loop |
boolean |
true |
The viewer goes from the last picture round to the first. |
query |
query |
string |
'' |
Shows only items matching these words (#tag for a tag). Mutable: the search box changes it. |
selectable |
selectable |
boolean |
false |
Pictures can be selected: click with Ctrl or Shift, the toolbar's Select, or Space. |
selection |
property only | string[] |
[] |
The ids selected. Mutable. |
slideshowInterval |
slideshow-interval |
number |
5 |
Seconds between pictures in the slideshow. |
sort |
sort |
BmxGallerySort |
'none' |
The order shown. Mutable. |
strings |
strings |
Partial<BmxGalleryStrings> | string |
— | Words to show instead of the English ones, as an object or JSON. |
toolbar |
toolbar |
boolean |
false |
Show the toolbar: search, layout, thumbnail size, sort and (with selectable) selection. |
Events
| Event | Detail | Description |
|---|---|---|
bmxAction |
BmxGalleryActionDetail |
Fired when one of actions is used in the viewer. |
bmxChange |
BmxGalleryChangeDetail |
Fired when the toolbar changes the layout, the size, the search or the order. |
bmxClose |
BmxGalleryItemDetail |
Fired when the viewer closes. |
bmxLoadMore |
void |
Fired as the reader nears the end while has-more is set: append the next items to items. |
bmxOpen |
BmxGalleryOpenDetail |
Fired before an item opens in the viewer. Cancel to show it some other way. |
bmxSelectionChange |
BmxGallerySelectionDetail |
Fired when the selection changes. |
bmxView |
BmxGalleryItemDetail |
Fired when the viewer moves to another item. |
Methods
| Method | Signature | Description |
|---|---|---|
clearSelection |
clearSelection() => Promise<void> |
Clears the selection. |
close |
close() => Promise<void> |
Closes the viewer. |
getItems |
getItems() => Promise<BmxGalleryItem[]> |
The items in the order shown, after the search. |
next |
next() => Promise<void> |
The viewer's next item. |
open |
open(item: string | number) => Promise<void> |
Opens an item (by id, or its place in the order shown) in the viewer. |
previous |
previous() => Promise<void> |
The viewer's previous item. |
scrollToItem |
scrollToItem(id: string, focus?: boolean) => Promise<void> |
Scrolls an item into view (and gives it focus with focus). |
selectAll |
selectAll() => Promise<void> |
Selects every item shown (after the search). |
setFocus |
setFocus() => Promise<void> |
Puts keyboard focus on the current thumbnail. |
Slots
| Slot | Description |
|---|---|
empty |
Shown when there are no items at all. |
CSS shadow parts
| Part | Description |
|---|---|
heading |
A group's heading. |
item |
A thumbnail; also item-selected. |
toolbar |
The search, layout, size and sort controls. |
viewer |
The viewer dialog. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-gallery-accent |
The selection ring and check, and the focus of a thumbnail. |
--bmx-gallery-height |
The gallery's height (it scrolls inside it); auto lets it grow with its content. |
--bmx-gallery-placeholder |
The colour a thumbnail shows while it loads (an item's color overrides it). |
--bmx-gallery-radius |
Corner radius of a thumbnail. |
--bmx-gallery-viewer-background |
The viewer's background. |
--bmx-gallery-viewer-color |
The viewer's text and icons. |
--bmx-gallery-viewer-panel |
The viewer's bars and its details panel. |