How to zoom an image on click in HTML

A link to the full-size file is the version that always works. A dialog element gives the overlay people expect, with keyboard handling included.

To zoom an image on click in HTML, the most reliable version is a link to the full-size file:

<a href="floorplan-full.jpg">
  <img src="floorplan-small.jpg" alt="Second floor plan" width="600">
</a>

Clicking opens the image on its own page, where the browser already provides zoom, pan and pinch. No script, and every browser behaviour people expect from a link keeps working.

A thumbnail opened as a full image in its own tab, with the browser zoom controls available.
A thumbnail opened as a full image in its own tab, with the browser zoom controls available.

Three ways to zoom an image on click

Approach Needs script Overlay Keyboard and Escape Best for
Link to the full file No No Free Reports, documentation, anything sent as a file
dialog with showModal Yes, three lines Yes Handled by the browser Galleries and product pages
Hidden checkbox toggle No Yes Partial Pages where script is blocked

Start at the top row and move down only when you need the overlay.

The dialog version

dialog is the element built for this. The browser supplies the backdrop, keeps focus inside, and closes on Escape.

<img src="chart-small.png" alt="Revenue by quarter" onclick="zoom.showModal()" style="cursor: zoom-in">

<dialog id="zoom" onclick="this.close()">
  <img src="chart-full.png" alt="Revenue by quarter, full size">
</dialog>

Click the backdrop and it closes, because the click lands on the dialog itself. Press Escape and it closes, with no code from you.

Style the backdrop with its own pseudo-element:

dialog::backdrop { background: rgba(0, 0, 0, 0.8); }
dialog { border: 0; padding: 0; background: none; max-width: 92vw; max-height: 92vh; }
dialog img { max-width: 100%; max-height: 92vh; height: auto; display: block; }

The max-height pair is the part to keep. Without it a tall image overflows the window and the bottom is unreachable.

An image opened in a dialog overlay with a dimmed backdrop behind it.
An image opened in a dialog overlay with a dimmed backdrop behind it.

The CSS-only version

Where scripts are not permitted, a hidden checkbox carries the state:

<input type="checkbox" id="z1" class="zoom-toggle" hidden>
<label for="z1"><img src="plan-small.jpg" alt="Floor plan"></label>
<label for="z1" class="zoom-overlay"><img src="plan-full.jpg" alt=""></label>
.zoom-overlay { display: none; }
.zoom-toggle:checked ~ .zoom-overlay {
  display: flex; position: fixed; inset: 0;
  background: rgba(0,0,0,.85); align-items: center; justify-content: center;
}
.zoom-overlay img { max-width: 92vw; max-height: 92vh; }

It works, and it has costs. A screen reader announces a checkbox, Escape does nothing, and the markup is hard for the next person to read.

Use it when script really is off the table. Otherwise the dialog version is shorter and better behaved.

Zooming in place

Sometimes the wanted effect is magnification within the frame rather than an overlay:

.tile { overflow: hidden; }
.tile img { transition: transform .25s; }
.tile:hover img, .tile:focus-within img { transform: scale(1.6); }

overflow: hidden on the container is what stops the enlarged picture spilling over its neighbours. Include :focus-within so keyboard users get the same effect.

Add a reduced-motion guard, because a scaling transition is exactly the sort of movement some readers switch off:

@media (prefers-reduced-motion: reduce) {
  .tile img { transition: none; }
}

Media queries covers that syntax.

Two files, not one

Point the thumbnail at a small file and the zoom at the large one. Using the same file for both means every reader downloads the full resolution whether or not they click.

<a href="site-photo-2400.jpg"><img src="site-photo-600.jpg" alt="Site entrance" width="600" loading="lazy"></a>

Set width and height on the thumbnail so the layout does not jump while it loads. Keeping an image at its aspect ratio explains how those attributes reserve the space.

Accessibility notes

  • Keep real alt text on the thumbnail. Describe the picture, not the interaction.
  • The enlarged copy inside an overlay can take alt="", since it repeats the thumbnail.
  • Anything clickable has to be reachable by keyboard. A link is by default; an onclick on a bare img is not.
  • cursor: zoom-in tells a mouse user the picture is clickable. It tells nobody else, so it is a supplement.

Alt text covers the writing side in more detail.

A thumbnail with a visible focus ring after tabbing to it, before the overlay is opened.
A thumbnail with a visible focus ring after tabbing to it, before the overlay is opened.

Getting the page to the reader

Zoom only helps if the page opens at all. A file sent as an attachment often arrives with the images stripped or the neighbouring files missing.

If the pictures have to travel inside the file, embedding images as base64 folds them in, at the cost of size.

The other route is an address. Paste the HTML into a NOS document and it renders as written, dialog and CSS included, with a link you can send in one message.

That link stays the same when you swap a photo or change the overlay. Turning HTML into a link is that step alone, and the HTML editor online lets you adjust the overlay sizing and see the result immediately.

One dialog per image gets repetitive quickly. For a set of pictures, use a single dialog and swap its source:

<div class="grid" onclick="open_(event)">
  <img src="a-small.jpg" data-full="a-full.jpg" alt="Foundation pour">
  <img src="b-small.jpg" data-full="b-full.jpg" alt="Steel frame">
</div>

<dialog id="zoom" onclick="this.close()"><img id="zi" alt=""></dialog>

<script>
  function open_(e) {
    var t = e.target;
    if (t.tagName !== 'IMG') return;
    document.getElementById('zi').src = t.dataset.full;
    document.getElementById('zi').alt = t.alt;
    document.getElementById('zoom').showModal();
  }
</script>

The click listener sits on the container rather than on each image. Adding a picture then needs no extra code, which matters when the gallery is built from a list.

Copying the alt across is the detail that is usually skipped. Without it the enlarged view is unlabelled for anyone using a screen reader.

A grid of thumbnails above the enlarged view opened from one of them.
A grid of thumbnails above the enlarged view opened from one of them.

What zoom does not fix

Zoom helps a reader look closer at a picture. It does not help when the underlying image is too small, and enlarging a 600 pixel screenshot only makes the blur bigger.

If the detail matters, export the original at the resolution people will need. A table screenshot is often better replaced with a real table, which stays sharp at any size and can be searched with the browser.

Editable HTML table is the quick route when the picture was a spreadsheet to begin with.

Choosing between the mechanisms in practice

A few situations settle the choice without much deliberation.

A report sent to a client. Use the link version. It survives being printed, forwarded and opened in whatever the reader has, and there is no script to be stripped.

A product page with several photographs. Use one dialog and swap the source. The overlay is what visitors expect, and the browser handles focus and Escape for you.

A page inside a system that blocks inline script. Use the checkbox toggle, and accept that the keyboard experience is weaker.

A diagram people need to read closely. Neither. Redraw it as SVG, which stays sharp at any zoom the browser offers, and give the reader the browser zoom they already know. SVG in HTML covers that route.

The pattern across all four is the same. Reach for the overlay when the page is a gallery, and for the plain link when the page is a document.

Questions people ask

What is the simplest image zoom that works everywhere?

Wrap the thumbnail in a link pointing at the full-size file. Clicking opens the image on its own, where the browser provides zoom and pan already. No script, no CSS, and it works with right-click, middle-click and keyboard.

How do I show the enlarged image in an overlay?

Use the dialog element and call showModal on it. The browser supplies the backdrop, traps focus inside, and closes on Escape. Those three behaviours are what a hand-rolled overlay usually gets wrong.

Can I do click-to-zoom with CSS only?

Yes, with a hidden checkbox and a label around the image. The checked state drives a transform or a full-screen overlay. It needs no script, but the markup is unusual and screen readers announce a checkbox.

Should the thumbnail and the zoomed image be the same file?

Not usually. Serve a small file for the thumbnail and the full-resolution one for the zoom. Otherwise every reader downloads the large file whether or not they ever click.

Keep reading