Instagram Gallery Lightbox — Free HTML CSS JS Snippet

Instagram Gallery Lightbox · Modals · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Square aspect grid
aspect-ratio keeps cells perfectly square.
Hover like counts
A scrim reveals engagement stats per tile.
Blurred lightbox
backdrop-filter dims and frosts the page.
Cross-dissolve swaps
Photos fade between each navigation.
Looping prev/next
Modulo wrapping cycles past the ends.
Keyboard and swipe
Arrows, Escape, and touch flicks.
Backdrop dismiss
Clicking outside the photo closes it.
Modal focus management
Focus moves in and restores on close.

About this UI Snippet

Instagram Gallery Lightbox — Square Grid with a Full Photo Viewer

Screenshot of the Instagram Gallery Lightbox snippet rendered live

This snippet recreates the Instagram profile experience: a tidy three-column grid of square photos that show like counts on hover, and a full-screen lightbox that opens on click with previous/next navigation, keyboard arrows, swipe gestures, and the usual dismissal patterns. It's built in plain HTML, CSS, and vanilla JavaScript, with accessibility and touch handled.

The square grid

The grid is a CSS grid-template-columns: repeat(3, 1fr) with a tight 5px gap, exactly like Instagram's profile layout. Each cell uses aspect-ratio: 1 so it stays perfectly square regardless of column width, and overflow: hidden with a cover-sized background keeps images cropped to the square without distortion. On hover, a dark scrim fades in (::after) and a centered overlay reveals the photo's like count and tag — the signature Instagram hover, which surfaces engagement stats without cluttering the resting grid.

Data-driven cells

All photos live in a PHOTOS array (here, gradient placeholders with like counts and tags, swappable for real image URLs). The grid is generated in a loop, with each cell's background set through a --bg custom property and its like count formatted with toLocaleString() so large numbers read as "3,402". Clicking a cell opens the lightbox at that index.

The lightbox

The viewer is a fixed-position overlay with a blurred, dimmed backdrop (backdrop-filter: blur). It toggles via an .open class that transitions opacity and visibility together — using visibility ensures the closed lightbox isn't focusable or interactive, which a plain opacity fade wouldn't guarantee. Inside, a large square photo shows the current image, with a caption row displaying the like count and the position indicator ("3 / 9").

Smooth image swaps

When you navigate, render() briefly fades the photo to opacity: 0, swaps the --bg after 120ms, then fades back in — so moving between images cross-dissolves instead of hard-cutting. The go(d) function wraps the index with modulo arithmetic ((current + d + length) % length), so Next from the last photo loops to the first and Previous from the first loops to the last.

Every dismissal and navigation path

The lightbox supports the full set of expected interactions: the close button, clicking the backdrop (but not the photo, via an e.target === box check), the Escape key, the on-screen prev/next arrows, and the Left/Right arrow keys. On touch, touchstart/touchend measure horizontal swipe distance and call go() past a 50px threshold, so you can flick between photos on a phone. On small screens the arrows reposition to overlay the photo edges.

Focus management

When the lightbox opens it stores the previously focused element, moves focus to the Next button, and restores focus to the original trigger on close — the correct focus pattern for a modal dialog, which is marked up with role="dialog" and aria-modal="true".

Customizing it

Replace the gradient placeholders with real image URLs in PHOTOS, change the grid to four columns, adjust the swipe threshold, or add captions and avatars to the lightbox. Pair it with a focus cards section or a photo gallery elsewhere on the page for a complete media experience.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to work out the focus-management and swipe logic by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the lightbox transitions both opacity and visibility together rather than opacity alone, or how the go function's modulo arithmetic makes prev and next wrap seamlessly past the first and last photos. The same assistant can help you optimize it — ask whether the render function's 120ms setTimeout-based cross-dissolve could instead be driven by a transitionend listener for more reliable timing, or how the grid and lightbox would need to change to lazy-load real photo files instead of instant CSS gradients. It is just as useful for extending the gallery: ask it to add pinch-to-zoom on the open photo, a double-tap-to-like heart animation like the real Instagram app, or a way to jump directly to a specific photo from a deep link on page load. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an Instagram-style square photo grid with a full-screen lightbox in plain HTML, CSS, and JavaScript — no libraries.

Requirements:
- A three-column CSS grid with a small fixed gap, where every cell uses aspect-ratio: 1 so it stays perfectly square at any column width, with overflow hidden and a cover-sized background image so photos crop into the square without distortion.
- On hover, each cell must reveal a dark scrim overlay and a centered row showing a like count (formatted with toLocaleString for thousands separators) and a tag, using an opacity transition driven by hover state, not JavaScript.
- Clicking a cell opens a fixed, full-viewport lightbox overlay with a blurred backdrop (backdrop-filter), toggled by adding and removing a class that transitions both opacity and visibility together, so the closed lightbox is neither visible nor focusable.
- The lightbox must show the current photo, a caption row with the like count and a "current / total" position indicator, a close button, and previous/next arrow buttons.
- Navigating between photos must wrap around at both ends using modulo arithmetic on the current index, and must briefly fade the photo to opacity 0, swap its content after roughly 120ms, then fade it back in for a cross-dissolve transition rather than a hard cut.
- Support every standard dismissal and navigation path: a close button click, clicking the backdrop but not the photo itself, the Escape key, the Left and Right arrow keys, and touch swipe gestures measured between touchstart and touchend with a minimum horizontal distance threshold.
- On open, store the previously focused element and move focus into the lightbox; on close, restore focus back to that stored element — the correct focus-management pattern for a modal dialog marked up with role="dialog" and aria-modal="true".

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA three-column square photo grid renders like an Instagram profile.
  2. 2
    Hover a tileA scrim fades in showing the like count and tag.
  3. 3
    Click a photoA blurred full-screen lightbox opens on that image.
  4. 4
    NavigateUse the arrows, Left/Right keys, or swipe to move between photos.
  5. 5
    Close itClick the backdrop, press the X, or hit Escape.
  6. 6
    Swap in real photosReplace the PHOTOS array with image URLs.

Real-world uses

Common Use Cases

Profile photo grids
An Instagram-style alternative to a photo gallery.
Portfolio galleries
Open work in a viewer below a portfolio hero.
Product image grids
Pair with a product card detail page.
Event recaps
Browse shots with a focus cards section nearby.
Travel blogs
Showcase trips with swipeable full-screen photos.
Lightbox demos
A reference for accessible modal image viewers.

Got questions?

Frequently Asked Questions

Each cell uses aspect-ratio: 1, so its height always matches its width no matter how the three flexible columns resize. overflow: hidden with a cover-sized background crops images into the square without stretching them, reproducing Instagram's tidy profile grid at any viewport width.

The go(d) function computes (current + d + length) % length. The modulo wraps the index, so pressing Next on the last photo lands on the first and Previous on the first lands on the last. The same function backs the arrow buttons, the Left/Right keys, and swipe gestures.

Fading opacity alone leaves the overlay technically present and focusable while invisible, so keyboard users could tab into hidden controls. Transitioning visibility alongside opacity makes the closed lightbox non-interactive and removes it from the tab order, while still allowing the fade because visibility is transitionable when paired with a delay.

It's marked up as role="dialog" with aria-modal="true". On open it records the previously focused element and moves focus to the Next button; on close it restores focus to the trigger. It closes on Escape and on backdrop click, and all controls have aria-labels. For production you'd also trap Tab focus within the dialog while it's open.

Render the grid from your photos array and keep current index and open state in component state. Move the keyboard and touch listeners into a mount effect with cleanup, and use refs for focus management. Drive the lightbox visibility from state rather than toggling a class. Swap the gradient placeholders for <img> elements with real src values. In Tailwind, use aspect-square, grid-cols-3, and backdrop-blur utilities.