Fandry UI
Block

Media gallery

A grid of pictures that fits whatever box it is in, and a full-screen viewer with swipe, pinch to zoom and keyboard control. Three pieces you can use apart: the behavior with no markup, a grid, and a viewer. Choose a picture to open it.

Columns Aspect ratio Fit Gap

Last change: None yet. In the viewer, the keyboard button (or ?) lists the keys. On a phone: swipe, pinch, double tap.

fandry-media-gallery {
  --fd-media-columns: 4;
  --fd-media-gap: 0.75rem;
  --fd-media-aspect-ratio: 1 / 1;
  --fd-media-fit: cover;
}
​

Install

Link to section Install

LWR / LWC OSS

npm install fandryui

<!-- nothing to copy: your bundler ships only what you use -->
<fandry-media-gallery></fandry-media-gallery>
​

Salesforce DX

npx fandry add media-gallery
# adds the block and everything it is built from
# to your package directory, as source you own
​

Usage

Link to section Usage
<fandry-media-gallery
  items={photos}
  loading={loading}
  onchange={handleChange}
></fandry-media-gallery>
​

An item is plain data:

photos = [
  {
    id: 'lake',              // unique and stable
    src: '/media/lake.jpg',  // the full file, for the viewer
    thumbnail: '/media/lake-400.jpg',
    alt: 'A pine forest above a calm bay.',
    caption: 'Above the bay',
    width: 1280,
    height: 960,
    loading: 'eager'         // only for pictures on screen at page load
  },
  ...
];
​

Props and methods

Link to section Props and methods
PropTypeDefaultDescription
itemsitem[][]What the gallery shows (see the item table below). Controlled: the block never changes it.
indexnumber0The current item. Set it to choose where the gallery starts; the block keeps it current as the user moves and reports each move through change.
loadingbooleanfalseWhile the items are being fetched: the grid shows placeholders and says it is busy.
loading-countnumber8How many placeholders.
messagesobject{}What the gallery says (the viewer's name, Close, Keyboard shortcuts, Previous, Next, "3 of 12", what a screen reader hears on moving, and the name of a tile whose picture has no alt text), e.g. to translate it: any key of DEFAULT_MEDIA_VIEWER_MESSAGES (fandry/mediaViewerState) or DEFAULT_MEDIA_GRID_MESSAGES (fandry/mediaGrid).
keys (slot)slotan English listThe keyboard shortcuts the viewer's keyboard button (or ?) shows. To translate it, slot a fandry-media-keys here with its text slots filled (title, previous-next, first-last, zoom, reset-zoom, pan, page, toggle, close, and the keys named with words: key-home, key-end, key-page-up, key-page-down, key-escape), or a list of your own.
show(index?) · close()methods—Open the viewer (on index, or on the current item) and close it.
next() · previous() · goTo(index)methods—Move the current item, with or without the viewer open.

Item

Link to section Item
KeyTypeDescription
idstringUnique and stable: the key each piece renders by.
srcstringThe full-size file, shown in the viewer.
altstringDescribes the picture for people who can't see it. '' for a decorative one.
thumbnailstringA smaller file for the grid. Without one the grid uses src (with srcset / sizes).
srcset · sizesstringAs on a native <img>, for the viewer.
width · heightnumberThe intrinsic size, so the browser reserves the space before the file arrives.
captionstringShown under the tile and under the picture in the viewer. Leave it out for none.
loading'lazy' | 'eager'Defaults to lazy. Set eager on the pictures that are on screen when the page opens.

Events

Link to section Events
EventdetailDescription
change{ index, item }The current item changed: a tile was chosen, or the user moved in the viewer.
togglebooleanThe viewer opened (true) or closed (false).

Tokens

Link to section Tokens

Layout and look are tokens, not props: set them on :root, on a section, or on one gallery.

TokenDefaultDescription
--fd-media-columns4The most tiles in a row.
--fd-media-gap--fd-space-3The space between tiles.
--fd-media-min-width8remThe narrowest a tile gets. A narrower box gets fewer columns, so the grid fits wherever it is put, with no breakpoints.
--fd-media-aspect-ratio1 / 1The shape every tile is cut to.
--fd-media-fitcovercover fills the tile and crops; contain shows the whole picture.
--fd-bg · --fd-textthe page'sThe viewer's surface and its text. Set them on the gallery for a dark viewer.

Parts

Link to section Parts
PartDescription
baseThe grid (a <ul>).
item · button · frame · image · captionOne tile, its fandry-button's <button>, the box the picture is cut to, the picture, its caption.
placeholderA tile's placeholder while loading.
viewer · backdrop · panelThe viewer, and the fandry-dialog it is drawn in.
toolbar · status · close-buttonThe bar at the top, the "3 of 12", the close button.
keys-button · keysThe keyboard button, and the box its list of shortcuts shows in.
stage · viewer-image · viewer-captionWhere the pictures slide, the current picture, its caption.
previous · nextThe arrows (with disabled at the first and last item).

Make it yours: a product gallery

Link to section Make it yours: a product gallery

A store's product page wants one big picture and a strip of thumbnails, not a grid. This one extends fandry/mediaGalleryState and writes only its own markup: the current picture, focus coming back after the viewer, the events and the full-screen viewer with swipe and zoom all come from the block.

// productGallery.js -- your markup, the block's behavior
import FdMediaGalleryState from 'fandry/mediaGalleryState';

export default class ProductGallery extends FdMediaGalleryState {
  // `current` is undefined while there are no items.
  get main() {
    return { src: this.current?.src ?? '', alt: this.current?.alt ?? '' };
  }

  handleThumb(event) {
    this.goTo(Number(event.currentTarget.dataset.thumb));
  }
}

<!-- productGallery.html -->
<fandry-button variant="ghost" data-index={currentIndex} onclick={handleSelect}>
  <img src={main.src} alt={main.alt} />
</fandry-button>
<ul>
  <template for:each={thumbs} for:item="thumb">
    <li key={thumb.key}>
      <fandry-button variant="ghost" data-thumb={thumb.index} onclick={handleThumb}>...</fandry-button>
    </li>
  </template>
</ul>
<fandry-media-viewer
  items={items}
  index={currentIndex}
  open={viewerOpen}
  messages={messages}
  onchange={handleViewerChange}
  ontoggle={handleViewerToggle}
></fandry-media-viewer>
​

The pieces

Link to section The pieces

fandry/mediaGalleryState has the behavior and no template. fandry/mediaGallery is that class plus a grid and a viewer. fandry/mediaGrid and fandry/mediaViewer each work on their own and know nothing of each other; what they share is the item shape in fandry/mediaItem. The viewer is split the same way: fandry/mediaViewerState has its keyboard, swipe and zoom and no template. fandry/mediaGestures is the swipe and pinch math, with no template. To change the markup, extend a state class, or, on Salesforce, edit the copy fandry add put in your project. Built from:dialogbuttonicontextskeleton