Image albums with Expo
Expo displays image collections as albums. A sidebar groups the albums. The main area shows the images in the selected album. Each image opens in a viewer.
Expo is included in OINK v1.3.0.
Open the example albums to try the controls. Use the Gallery component for an image grid inside a text page.
Create an album
Use one directory for each album. Put the images in that directory’s images/
folder. One index.md describes the album. Filenames supply the default image titles.
Expo does not require a Markdown or YAML file for each image.
-
Create the section entry:
content/photos/_index.md -
Create a topic entry for each group of albums:
content/photos/nature/_index.md -
Create the album page:
content/photos/nature/field-notes/index.md -
Put the images in the album’s
images/folder.
The section lists its child topics and albums. The sidebar follows the same content tree. On narrow screens, open the navigation drawer to select an album. The album body is optional. If supplied, it appears above the images.
Split large collections into separate albums. Expo has no automatic image pagination. A section card uses the first sorted image as its cover. A topic without images uses the first available image from its descendant albums. The cover fills a 4:3 frame and can crop the preview.
Add image metadata
Add a resources entry to the album front matter when an image needs a title,
description, credit, or fixed position. The src path is relative to the album
directory.
| Field | Use |
|---|---|
title |
Image title. If omitted, Expo derives a title from the filename |
params.alt |
Text that describes the image. If omitted or empty, it uses the title |
params.caption |
Plain-text description |
params.byline |
Plain-text credit |
params.weight |
Numeric image order. Lower values come first. The default is 0 |
params.id |
Stable image ID within the album |
params.original |
Optional path to another image in the same album bundle |
Images with equal weights keep the resource name order. To set a complete order, give each image a weight. Images without a weight come before positive weights.
Use ASCII letters or digits for the first ID character. The remaining characters can
also include _ and -. Keep IDs unique within an album. The example creates
/photos/nature/field-notes/#img-fern. This link stays valid after a filename
change if the album URL and explicit ID stay the same. Without an explicit ID,
Expo uses a filename hash. Renaming that file changes the link.
To use a separate original, put it in originals/ and set
params.original: originals/fern.jpg. Keep the preview and original at the same
aspect ratio. Keep originals outside the expo.images pattern to avoid duplicate
entries. Expo reads bundled images; it does not fetch remote files for an album.
Set the defaults
Set site defaults in hugo.yml:
| Key | Allowed value | Built-in default |
|---|---|---|
images |
A pattern for image resources in the album bundle | images/* |
mode |
wall, cards, or seamless |
wall |
size |
An integer from 160 to 360, in CSS pixels |
240 |
Use a top-level expo block in album front matter to replace the site block:
This override replaces the whole block. Omitted fields use built-in defaults,
not values from the site block. A section can supply the block through cascade.
The size is a layout target, not a fixed width and height for every image.
The browser saves the reader’s layout and size choices for the same site origin. These choices take precedence over author defaults. They remain in use after a reload, an album change, or an appearance change. If browser storage is unavailable, Expo uses the author defaults when the page loads.
Select a layout
| Mode | Display |
|---|---|
wall |
Rows of image tiles with 6 CSS pixels between them |
cards |
Columns of cards with visible titles, descriptions, and credits |
seamless |
Rows of image tiles with no gaps between them |
The toolbar has three layout icons, a play/pause button, and an image-size slider. On devices with a mouse, Wall and Seamless show captions on hover or keyboard focus. Images with extreme proportions can have space inside their tiles. The album tiles contain the whole image.
GIFs play by default. Select Pause to show still previews. If the system requests reduced motion, Expo starts with still previews. Select Play to start them. The play button is disabled when the album has no GIFs. Images load as they approach the visible area; large GIF files still require their full transfer size.
Use the image viewer
Select an image to open the viewer. A small image starts at its original size. A large image shrinks until both dimensions fit the available image area.
| Control | Action |
|---|---|
| Reset | Restore the initial size and center the image |
| Expand to fit | Enlarge or shrink the image until its limiting edge fits the image area |
| Zoom out / Zoom in | Divide or multiply the current scale by 1.25 |
| Open original | Open the original image file in a new tab |
| Locate in album | Close the viewer and move to this image in the album |
| Previous / Next | Show the adjacent image in the same album |
| Close | Close the viewer and restore focus to an image link |
Expand to fit keeps the whole image visible. It does not switch the browser to fullscreen. Manual zoom can make the image larger than the viewing area. Hold the left mouse button and drag to move the enlarged image. Movement stops at the image bounds. On a touch screen, an enlarged image uses native scrolling. When the image fits, swipe horizontally with one finger to change images; pinching keeps native browser behavior. Double-click alternates between the initial fit and a closer view: a reduced image opens at 100% under the pointer, while an image already at its original size expands to fit.
Changing images resets the scale and position. Previous and Next stop at the ends of the album. The percentage uses CSS pixels: at 100%, one source pixel occupies one CSS pixel.
Use keyboard shortcuts
These shortcuts work while the viewer is open. They take precedence over the page’s single-key navigation. They do not handle text input, input method composition, or Ctrl/Command/Alt combinations.
| Key | Action |
|---|---|
| W / A / S / D | Move the enlarged image up / left / down / right |
| Q or H | Previous image |
| E or L | Next image |
| J or − | Zoom out |
| K or + | Zoom in |
| R or 0 | Reset the size and position |
| F | Expand to fit |
| Escape | Close the viewer |
WASD moves the image by 80 CSS pixels per press, only along an axis that
overflows. Left and Right arrow keys change images when horizontal scrolling
is unnecessary. When the image is wider than the viewing area, the arrows
retain native scrolling. The keyboard_nav setting controls page shortcuts;
it does not disable the viewer controls.
Use the site appearance
Expo uses Paper, Slate, Ink, and Terminal with the site’s light or dark mode. Select these options in the shared navigation. The album body has its image toolbar and no article action menu.
To expose all four presets and keep the top navbar visible, use:
Expo uses the full available page width by default and has no right-hand outline.
Set page_width in page front matter if the album needs a narrower body. If your
site overrides params.ui.shell_types, include expo to retain its sidebar.
Translate and export an album
Add index.zh.md beside index.md for a Chinese album. Add _index.zh.md for
each translated section. Both languages can share the image files. Translate
the title, description, and resource metadata in each page. Keep image IDs the
same when they refer to the same image.
HTML works without JavaScript: image links open the original files, and captions remain visible. When enabled, Print uses still previews. Markdown and LLMSFULL include image links and metadata. RSS includes static image descriptions. See Print and Agent outputs for output configuration.
Check an album
- Run
hugo --printPathWarnings --panicOnWarningwith a theme version that includes Expo. - Open each album and confirm its image order, titles, and descriptions.
- Test a small image, a wide image, a long image, and a GIF if the album has one.
- Test Reset, Expand to fit, zoom, dragging, keyboard navigation, and Locate in album.
- Check both languages, narrow screens, the four presets, and light/dark mode.
If an album is empty, check its type, bundle structure, and expo.images pattern.
If a default seems to have no effect, check the stored reader choice and the
whole-block override rule. For required behavior and maintainer checks, see the
Expo contract and design decision.