Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsUse a keyboard-operable <button> around the thumbnail, open a native <dialog> with showModal(), and place the larger image plus an explicit close button inside it. For a local file selected with <input type="file">, create an object URL from the selected File and assign it to the preview image. The two sources use the same dialog pattern but have different loading and cleanup code.
Choose the image source first
“Preview on click” can mean either enlarging an image that is already on the page or showing a file a visitor has just selected. Decide which case you have before writing the event handler.
| Use case | Image source | What the click handler does | Cleanup |
|---|---|---|---|
| Existing thumbnail | A full-size URL already known to the page | Set the dialog image source (if needed) and call showModal() |
Usually none; the URL remains valid |
| Local file selection | input.files[0] |
Create an object URL, assign it to the dialog image, then call showModal() |
Call URL.revokeObjectURL() when replacing or removing the preview |
A native dialog is preferable to a clickable bare <img>: the button supplies a familiar keyboard target, while showModal() provides modal behavior and Escape dismissal. MDN’s dialog guidance covers the required closing mechanism and focus behavior: MDN, <dialog>.
Preview an existing image with a native dialog
Complete HTML, CSS and JavaScript
This example keeps the thumbnail and the larger image as separate files. The trigger has an accessible name, and the large image retains useful alternative text.
#1 Best Overall
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Image preview</title>
<style>
.thumb-button {
border: 0;
padding: 0;
background: transparent;
cursor: zoom-in;
}
.thumb-button img {
display: block;
width: 220px;
height: auto;
}
#imagePreview {
width: min(92vw, 900px);
max-width: none;
padding: 1rem;
border: 0;
border-radius: .5rem;
}
#imagePreview::backdrop {
background: rgb(0 0 0 / .78);
}
#imagePreview img {
display: block;
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
margin: 1rem auto 0;
}
#closePreview {
display: block;
margin-left: auto;
}
</style>
</head>
<body>
<button id="openPreview" class="thumb-button" type="button"
aria-label="Preview mountain at full size">
<img src="mountain-thumb.jpg" alt="Mountain at sunset">
</button>
<dialog id="imagePreview" aria-label="Image preview">
<button id="closePreview" type="button" autofocus>
Close image preview
</button>
<img id="previewImage" src="mountain-large.jpg"
alt="Mountain at sunset">
</dialog>
<script>
const dialog = document.querySelector('#imagePreview');
const openButton = document.querySelector('#openPreview');
const closeButton = document.querySelector('#closePreview');
openButton.addEventListener('click', () => {
dialog.showModal();
});
closeButton.addEventListener('click', () => {
dialog.close();
});
</script>
</body>
</html>
showModal() makes the rest of the document inert while the dialog is open. A user can activate the explicit close button or press Escape. The autofocus attribute places focus on the close control when the dialog opens; if your design needs a different starting point, set focus deliberately in the opening handler.
Use one dialog for a gallery
For several thumbnails, store the full-size URL in a data attribute and update one dialog image before opening it. This avoids creating a dialog for every thumbnail.
<div class="gallery">
<button class="gallery-item" type="button"
data-full="photos/city-large.jpg"
data-alt="City skyline at dusk">
<img src="photos/city-thumb.jpg" alt="City skyline at dusk">
</button>
<button class="gallery-item" type="button"
data-full="photos/forest-large.jpg"
data-alt="Forest trail in autumn">
<img src="photos/forest-thumb.jpg" alt="Forest trail in autumn">
</button>
</div>
<dialog id="galleryDialog" aria-label="Gallery image preview">
<button id="galleryClose" type="button" autofocus>Close</button>
<img id="galleryPreview" alt="">
</dialog>
<script>
const galleryDialog = document.querySelector('#galleryDialog');
const galleryPreview = document.querySelector('#galleryPreview');
const galleryClose = document.querySelector('#galleryClose');
document.querySelectorAll('.gallery-item').forEach((button) => {
button.addEventListener('click', () => {
galleryPreview.src = button.dataset.full;
galleryPreview.alt = button.dataset.alt || '';
galleryDialog.showModal();
});
});
galleryClose.addEventListener('click', () => galleryDialog.close());
</script>
Keep each thumbnail’s alternative text meaningful. If the full-size image conveys the same information, reuse that description; if it reveals additional detail, describe the larger view accurately.
Rank #2
Preview a local file before uploading
Object URL method
A file input gives you a File object, not a web URL. URL.createObjectURL(file) creates a temporary URL that an <img> can display. Track that URL and revoke it when it is replaced or when the preview is removed. Do not revoke it immediately after assigning src if the user still needs to view or interact with the image. MDN documents this lifecycle in Using files from web applications.
Recommended Free Tools
<label for="imageFile">Choose an image</label>
<input id="imageFile" type="file" accept="image/*">
<dialog id="fileDialog" aria-label="Selected image preview">
<button id="fileClose" type="button" autofocus>Close</button>
<img id="filePreview" alt="Selected image preview">
</dialog>
<script>
const fileInput = document.querySelector('#imageFile');
const fileDialog = document.querySelector('#fileDialog');
const filePreview = document.querySelector('#filePreview');
const fileClose = document.querySelector('#fileClose');
let previewUrl = null;
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
fileInput.value = '';
return;
}
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
}
previewUrl = URL.createObjectURL(file);
filePreview.src = previewUrl;
filePreview.alt = `Preview of ${file.name}`;
fileDialog.showModal();
});
fileClose.addEventListener('click', () => fileDialog.close());
window.addEventListener('beforeunload', () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
});
</script>
The accept="image/*" attribute filters the chooser, but it is not a security boundary. Validate the MIME type and, on the server, validate the uploaded bytes again before storing or processing the file.
FileReader data URL method
FileReader.readAsDataURL(file) is another documented approach. After the reader’s load event, assign reader.result to the image source. The API references document both methods but do not establish a universal performance winner, so choose according to your application and memory constraints.
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.addEventListener('load', () => {
filePreview.src = reader.result;
filePreview.alt = `Preview of ${file.name}`;
fileDialog.showModal();
});
reader.readAsDataURL(file);
});
Unlike an object URL, a data URL contains the encoded file data in the string itself. It does not have an object-URL revocation step, but very large files still consume memory while encoded and displayed. See MDN’s readAsDataURL() reference for the event-based API.
Make the preview usable on every screen
- Constrain the dialog image with
max-width: 90vwandmax-height: 80vhso a portrait or landscape image fits without forcing horizontal scrolling. - Keep the close button visible and reachable near the top of the dialog. MDN emphasizes that every dialog needs a closing mechanism that also works on devices without a physical keyboard: dialog accessibility guidance.
- Let the browser handle Escape for a modal dialog, but do not rely on Escape alone; touch users need a visible control.
- Return focus to the button that opened the dialog when your surrounding interface makes that movement important. Store the opening button in a variable, then call its
focus()method afterclose(). - Use
object-fit: containif you give the preview image a fixed box. Avoid stretching the source by setting both width and height without preserving its ratio.
Native dialog versus a custom overlay
<dialog> with showModal() supplies modal behavior, inert background content and standard dismissal semantics. The element is broadly available in current browsers, with MDN noting availability across browsers since March 2022; individual dialog-related features can still vary, so check the compatibility table for the browsers you support: MDN dialog reference.
Free tools Windows power users keep installed
One-click scans. No signup required.
A custom overlay made from a <div> is possible when you need a specialized animation or legacy fallback, but it is not equivalent to adding role="dialog" or aria-modal="true". You must implement focus movement, background inertness, Escape handling, a close mechanism and return focus yourself. MDN explains the limits of these ARIA attributes in the dialog role and aria-modal references.
Rank #4
Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
dialog.showModal is not a function |
The selected element is not a native dialog, or the target browser is too old. | Confirm the element is <dialog>, query it after the markup exists, and check support for your target browsers. A custom fallback requires full modal behavior, not just an ARIA role. |
| The thumbnail opens a new page instead of a modal | The button handler is missing or the code runs before the DOM exists. | Load the script after the markup or use DOMContentLoaded, then attach the click listener to the button. |
| The dialog opens but the image is blank | The full-size URL is wrong, blocked, or the object URL was revoked too soon. | Inspect the image request, verify the URL, and keep the object URL until the preview is closed or replaced. |
| Every new file preview leaks memory | Old object URLs are never released. | Store the current URL and call URL.revokeObjectURL() before assigning a replacement. |
| Users cannot dismiss the preview on a phone | Only Escape or clicking the backdrop is supported. | Keep a visible, focusable close button inside the dialog. |
| The file chooser accepts a non-image | accept is only a chooser hint. |
Check file.type in the browser and validate the file again on the server. |
Performance, security and reliability notes
- Use appropriately sized thumbnail files so the page does not download full-resolution images before a user asks to see them. Load the large source when the dialog opens if your gallery is large.
- For local previews, no upload occurs merely because an object URL or data URL is displayed. Upload happens only when your code sends the file to a server.
- Keep the preview dialog’s image source separate from user-controlled HTML. Assign URLs through the
srcproperty and text throughalt; do not concatenate untrusted values into markup. - Handle image errors with an
errorlistener if a remote source can disappear. Replace the preview with a short status message and leave the close control available. - Do not claim that one local-preview method is always faster. Object URLs and data URLs have different lifecycles, and the supplied API references provide no universal benchmark.
Or skip the browser setup
If your goal is to generate a screenshot of a web page for documentation, testing or an AI workflow—not to let a visitor inspect a local file—ScreenshotNeo provides a single-request alternative. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
Use the API documentation at screenshotneo.com/docs/ for authentication and options. The same endpoint supports PNG, JPEG, WebP or PDF output.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));
ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF page controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request and resource blocking, headers, cookies, user agents, Authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage APIs and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work.
Every plan includes every feature: 1,000 shots per month are free with no card; paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000 and Business $249 for 1,000,000. Yearly billing provides two months free. Create a free ScreenshotNeo account to start with the 1,000 monthly screenshots.
Best Value
Frequently Asked Questions
Can I open the preview when the user presses Enter?
Yes. A native button is already keyboard-activatable: focus it and press Enter or Space. Avoid replacing it with a bare image, which has no built-in keyboard interaction.
Can one file input show several selected images?
Add the multiple attribute, iterate over input.files, and create a separate preview record for each file. Keep a revocable object URL for every displayed file and release each URL when that preview is removed.
How do I close the dialog when a user clicks outside the image?
You can listen for a click on the dialog and close only when event.target === dialog. Keep the explicit close button even if you add this convenience behavior, because it is the dependable dismissal method.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

