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.

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.

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
alttext 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
onclickon a bareimgis not. cursor: zoom-intells 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.

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.
Zoom in a gallery
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.

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.