ImageGallery
Horizontal gallery with scroll, drag and arrows
Preview
In real use it bleeds to 100vw (it breaks out of the content column). On desktop it is constrained to the frame here so it doesn't slide under the sidebar (scroll and arrows are the real ones); on mobile it bleeds full width as in production, which is why the next slide peeks in.
Behavior
| Context | Behavior |
|---|---|
| Mobile (≤767px) | Touch scroll, snap to start, 90vw slides with peek. Arrows hidden. |
| Desktop (≥768px) | Mouse drag, arrows, fixed 400px height. |
| The group fits on screen | is-fit: scroll disabled, track centered, arrows hidden. |
| At start / at end | The corresponding arrow hides (is-hidden). |
| Image load | Each image fades in; the fit is re-measured as they load. |
| Drag > 5px | The final click is suppressed: dragging doesn't trigger a link. |
Lenis (optional, per site)
Lenis is not a system dependency. The gallery uses native scroll APIs only. The container carries data-lenis-prevent-touch: if the site uses Lenis, it hands horizontal touch scroll back to the browser; if not, the attribute is inert and the component behaves the same. It degrades cleanly with no configuration.
Props
| Prop | Type | Default |
|---|---|---|
| images | { src, alt }[] | n/a |
| label | string | 'Galería de imágenes' |
| prevLabel | string | 'Imagen anterior' |
| nextLabel | string | 'Imagen siguiente' |
Constraints
- Bleeds to
100vw: it breaks out of any content column, on purpose. - Fixed desktop height (400px,
object-fit: cover): portrait and landscape are cropped to that band. - Assumes a consistent aspect ratio across the images in a group. On mobile the slides use
height: autoand the trackalign-items: stretch: they all match the tallest, so an image with a different ratio leaves a gap below. Feed each gallery images with the same ratio. - Scrollbar hidden by design; arrows and drag are the affordance on desktop.
- Labels in Spanish by default; other languages pass
label/prevLabel/nextLabel(as this page does).
Decisions
| Decision | Why |
|---|---|
| SVG arrows, not Unicode ← / → | Unicode arrows render as color emoji on some platforms: the same bug LinkArrow avoids. SVG with currentColor stays typographic. Two components of the system can't contradict each other. |
| Lenis as an inert hook, not a dependency | Removing it would break touch scroll on sites with Lenis; requiring it would impose a dependency the system doesn't own. Inert-when-absent satisfies both. |
| Labels as a prop, Spanish by default | The reference site and the workbench were Spanish; the default leaves current consumers unchanged. Other languages override it. |
| Re-measure on image load | The track width depends on the images; measuring before they load gives the wrong result, and a ResizeObserver can't recover it once is-fit fixes the width. |
| Suppress click after drag | Without it, dragging over a linked image would navigate on release. The 5px threshold separates the intent. |