Plate № 23 · UX · media / lightbox
A pattern from the gf.cx specimen book
Thumbnail → modal enlargement
"can we convert this to a pattern-modal, and store it on assets.gf, and talk about it on patterns.gf?"
The problem
A page grows a thumbnail — a locator map, a facility photo, a hand-drawn diagram — and the reader wants it bigger. So you build a modal: an overlay, a close button, backdrop-and-Escape handling, focus return, body-scroll lock, a caption. It works. Then the next page needs one, and the one after that, and each gets its own slightly-different copy: this one traps focus, that one forgets to restore scroll, a third uses a z-index that a tooltip pokes through. The behaviour is identical every time; only the bugs differ.
The same drift the provenance footer and back-to-top icon kill, one layer up: a UI affordance that should be one shared thing, hand-copied into many. The fix is the same — lift it to a single CDN-hosted primitive, mark the thumbnail with a data attribute, and let the shelf own the overlay.
The element, in-page
The live specimen below runs in a sandboxed iframe (the pattern-preview primitive). Click either thumbnail to open the enlargement; close with the ×, a click on the backdrop, or Escape. Tab to a thumbnail and press Enter — it opens too. One overlay serves every thumbnail on the page.
How you use it
Link the shared css + js once, then mark any thumbnail data-modal. No overlay markup, no per-page script:
<link rel="stylesheet" href="https://assets.gf.cx/modal/modal.css">
<script src="https://assets.gf.cx/modal/modal.js" defer></script>
<figure class="modal-thumb" data-modal
data-modal-src="…/map.png"
data-modal-caption="Locator — Josefstrasse 225. © OpenStreetMap">
<img src="…/map.png" alt="Locator map">
<figcaption>Josefstrasse 225</figcaption>
</figure>
What gets enlarged, first match wins: data-modal-src (an image URL) → data-modal-node (a CSS selector to clone — e.g. an inline <svg> shown small) → otherwise the trigger's own first <svg> or <img>. Caption falls back to the trigger's <figcaption>.
Where it applies
- Any thumbnail that rewards a closer look. Locator maps, facility photos, charts, hand-drawn diagrams — a
<figure>the reader might want full-size. Origin: srvc.gf.cx/transit, where facility Visuals disclosures pair an OpenStreetMap locator with a building photo, and the leaf-spine fabric primer enlarges a hand-authored inline<svg>. - Inline SVG, not just raster. The
data-modal-nodeclone path enlarges vector diagrams crisply at any size — the small thumbnail and the enlargement are the same source node, so there is nothing to keep in sync. - Article and teardown surfaces. Any page built from the shared shelf that embeds figures — video teardowns, notes, dossiers — gets enlargement by adding one attribute.
- Skip where a thumbnail is already a link to a fuller page (the click belongs to navigation), or where the image is decorative and there is nothing to inspect.
Reusable elements
The named, copyable pieces — lift any one without the others:
- Shared asset
assets.gf.cx/modal/modal.css+modal.js— the complete behaviour: on-demand overlay injection, event-delegated open, image + inline-SVG enlargement, caption, ×/backdrop/Escape close, focus return, body-scroll lock, singleton guard, print hiding. Two lines in the head/foot. - The
data-modalattribute API —data-modal(marker) + optionaldata-modal-src/data-modal-node/data-modal-caption/data-modal-alt. House convention, same shape asdata-tip/data-site-nav. - Node-clone enlargement —
data-modal-node="#sel"deep-clones an existing DOM node (stripsid/width/height, scales tomin(86vw, 900px)) so an inline<svg>shown small enlarges from a single source. - Progressive-enhancement a11y — triggers lacking them are auto-given
role="button"+tabindex="0"; aMutationObserverwires thumbnails added after load. A bare<figure data-modal>is keyboard-operable with no extra markup. - The
.modal-thumbaffordance — the framed,cursor: zoom-in, focus-ringed thumbnail look, themed off local CSS vars; width/float stay page-specific.
Reference
- Source
- Dan, 2026-07-09 — "convert this to a pattern-modal, store it on assets.gf, talk about it on patterns.gf". Distilled from the bespoke modal built for
srvc.gf.cx/transitthe same day, then lifted to the shelf. - In use / example
- srvc.gf.cx/transit — facility Visuals disclosures (map + building photo) and the leaf-spine fabric diagram all open through the shared modal. Shelf source:
assets.gf.cx/modal/. - Reusable elements
- Shared CDN css+js ·
data-modalattribute API · node-clone enlargement · progressive-enhancement a11y ·.modal-thumbaffordance (listed above). - Origin
- An event-delegated modal hand-wired into
srvc.gf.cx/transit(2026-07-09) for locator maps + an inline-SVG diagram. Generalised the same day to a shelf primitive. Sibling of the back-to-top jump icon and built-with provenance footer — the same single-source, control-plane discipline applied to a different page layer. - Build cost
- ~30 min to lift the bespoke modal to the shared css+js and migrate the origin page; ~1 line + a
data-modalattribute per new thumbnail thereafter.