What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
To enlarge an image already on a web page, put its thumbnail inside a real <button> and open a native <dialog> when the button is clicked. For a file the visitor has just selected, first point an image element at that local file with an object URL, then use the same dialog pattern. These are different image sources, but they can share the same accessible preview interface.
Preview an image that is already on the page
A thumbnail and a full-size image can be connected with a button and a modal dialog. The button gives keyboard users a familiar control; showModal() opens the dialog modally, and close() dismisses it. A modal opened with showModal() can also be dismissed with Escape, and the browser makes the rest of the page inert while it is open. See MDN’s dialog reference.
Complete example
Save this as an HTML file and replace the example image paths with paths to your own thumbnail and larger image. It is an illustrative pattern, not a claim that the code has been tested in every browser.
<!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>
.thumbnail-button {
padding: 0;
border: 0;
background: none;
cursor: zoom-in;
}
.thumbnail-button img {
display: block;
max-width: 18rem;
height: auto;
}
dialog {
border: 0;
border-radius: .5rem;
padding: 1rem;
max-width: 95vw;
max-height: 95vh;
}
dialog::backdrop {
background: rgb(0 0 0 / 75%);
}
.preview-image {
display: block;
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
object-fit: contain;
}
.dialog-close {
display: block;
margin: 0 0 .75rem auto;
}
</style>
</head>
<body>
<button class="thumbnail-button" id="openPreview" 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 class="dialog-close" id="closePreview" type="button" autofocus>
Close image preview
</button>
<img class="preview-image" 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>
Make the trigger and image meaningful
Do not make a bare image the only interactive control. Wrapping the thumbnail in a button provides a keyboard-operable trigger with the expected control semantics. Give the thumbnail useful alternative text, and give the button an accessible name that makes the action clear, such as “Preview mountain at full size.” The larger image should also have appropriate alternative text; if it conveys no additional information beyond the thumbnail, avoid adding redundant or misleading wording.
#1 Best Overall
Size the preview for the viewport
The example limits the dialog and image to viewport dimensions so a large source image does not spill beyond a small screen. max-width: 90vw and max-height: 80vh are example constraints, not universal values. Adjust them to the layout you need. The backdrop can be styled with the dialog’s ::backdrop pseudo-element.
Handle dialog focus and dismissal accessibly
A visible close button matters even though Escape works for a modal dialog: not every visitor has a physical keyboard, and touch users need an obvious way out. MDN’s accessibility guidance says, “It is important to provide a closing mechanism for every <dialog> element, and to ensure that this works on devices that might not have a physical keyboard.” See MDN’s dialog accessibility guidance.
Choose the initial focus target
Focus behavior when a dialog opens depends on its contents and browser behavior. In the example, autofocus puts focus on the close button, making the dismissal action immediately available. That is a deliberate choice, not necessarily the best target for every design. Choose the initial focus target that best supports the task, and check that focus returns sensibly to the trigger when the dialog closes. MDN discusses initial focus and recommends selecting an appropriate target in its dialog reference.
Rank #2
Use native dialog behavior where possible
A native dialog opened with showModal() has modal behavior; calling show() instead opens a non-modal dialog. A custom overlay made from a <div> does not acquire modal focus management just because it has role="dialog" or aria-modal="true". Those attributes communicate semantics; they do not implement focus handling or the other expected interaction behavior. See MDN on the dialog role and aria-modal.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Preview an image selected from the visitor’s device
A local file is not a URL on your server. After the visitor chooses a file, read the first selected File from the input’s files list, create a temporary object URL, and assign it to an image’s src. That image can then be opened in the same dialog. The example below also allows a new selection to replace the previous one.
Complete local-file example using an object URL
<label for="imageFile">Choose an image</label>
<input id="imageFile" type="file" accept="image/*">
<button id="openLocalPreview" type="button" hidden>
Preview selected image
</button>
<dialog id="localPreviewDialog" aria-label="Selected image preview">
<button id="closeLocalPreview" type="button" autofocus>
Close image preview
</button>
<img id="localPreviewImage" alt="Selected image preview"
style="max-width: 90vw; max-height: 80vh; object-fit: contain;">
</dialog>
<script>
const fileInput = document.querySelector("#imageFile");
const openLocalPreview = document.querySelector("#openLocalPreview");
const localDialog = document.querySelector("#localPreviewDialog");
const localImage = document.querySelector("#localPreviewImage");
const closeLocalPreview = document.querySelector("#closeLocalPreview");
let currentObjectUrl = null;
fileInput.addEventListener("change", () => {
const file = fileInput.files[0];
if (!file) {
openLocalPreview.hidden = true;
localImage.removeAttribute("src");
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
return;
}
if (!file.type.startsWith("image/")) {
alert("Please choose an image file.");
fileInput.value = "";
openLocalPreview.hidden = true;
localImage.removeAttribute("src");
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
return;
}
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
}
currentObjectUrl = URL.createObjectURL(file);
localImage.src = currentObjectUrl;
openLocalPreview.hidden = false;
});
openLocalPreview.addEventListener("click", () => {
if (currentObjectUrl) {
localDialog.showModal();
}
});
closeLocalPreview.addEventListener("click", () => {
localDialog.close();
});
window.addEventListener("pagehide", () => {
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
});
</script>
The accept="image/*" attribute helps the file picker present image choices, but it should not be treated as a security boundary. The example checks the file’s reported MIME type before previewing it; applications that upload files should also validate them on the server. The browser’s file API documentation describes accessing selected files and using object URLs: MDN: Using files from web applications.
Keep the object URL for as long as the preview is available
URL.createObjectURL(file) returns a temporary URL for the selected file. Revoke the prior URL when a new file replaces it, or when the preview is removed or the page is being left. Do not revoke it immediately after assigning it to src if the visitor should still be able to open and view that image. MDN documents object URLs and their lifecycle in its file API guide.
Use FileReader when a data URL suits your code
FileReader.readAsDataURL(file) is another documented way to preview a selected file. It reads the file and, when the load event fires, exposes a data URL in reader.result; assign that result to the image’s src.
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.addEventListener("load", () => {
localImage.src = reader.result;
});
reader.readAsDataURL(file);
}
This short fragment assumes fileInput and localImage refer to the input and image elements from the previous example. See MDN’s readAsDataURL() reference. The documented approaches do not establish a universal performance winner; choose based on how your application uses the file and, if using an object URL, manage its lifetime.
Rank #4
Support and compatibility considerations
MDN describes <dialog> as widely available across browsers since March 2022, while noting that some parts may vary in support. That availability statement is not a guarantee for every dialog-related feature or every browser version. Check MDN’s current compatibility information for the actual target browsers and the specific behaviors you rely on, particularly if your audience uses older or embedded browsers: MDN: The Dialog element.
If your target environment does not support the specific dialog behavior you need, decide whether to provide a fallback or limit support for that environment. Do not assume that adding ARIA attributes to a custom overlay supplies the keyboard, focus, and modality behavior of a native modal; those behaviors need to be implemented as well.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshoot common preview problems
- Clicking the thumbnail does nothing: Confirm the event listener is attached after the elements exist in the document, the selector matches the correct button, and the dialog is a
<dialog>element. Open the browser console for JavaScript errors. showModal()reports an error: Check that the selected element is a dialog and is not already open in a conflicting state. If targeting browsers with varying support, verify the dialog method against your actual browser requirements.- The larger image is missing: Check that the full-size image path is valid and that the image loads directly in the browser. For a local preview, confirm a file was selected and that the input’s
files[0]exists. - A selected file is rejected: The sample requires a MIME type beginning with
image/. Some files may have missing or unexpected type metadata; adapt validation to the formats your application supports rather than blindly accepting arbitrary files. - A replaced file no longer previews: Ensure the code creates a fresh object URL for the new file and does not revoke that URL while it remains in use. Revoke the old URL only after replacing its source.
- Visitors cannot find how to close the preview: Keep the visible close button. Escape dismissal for a modal does not replace a close control usable by touch and pointer visitors.
- Keyboard focus behaves unexpectedly: Choose a purposeful initial focus target and confirm the focus order and return-to-trigger behavior. A custom overlay needs its own dialog interaction implementation; ARIA labels alone do not supply it.
Or skip the browser setup
If what you need is a website screenshot rather than an interactive on-page image preview, ScreenshotNeo returns an image or PDF from one GET request. It is a separate workflow: it captures a website; it does not add a click-to-preview dialog to your page.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
For example, this cURL request saves a screenshot of Stripe as WebP:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for API parameters. Before capture, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of these steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with response headers identifying the page verdict and billing status. An MCP server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Frequently Asked Questions
Can I use one dialog for a gallery of thumbnails?
Yes. Keep one dialog and update its large image source and alternative text for the activated thumbnail before opening it; each button should identify which image it represents.
Does a local-file preview upload the image?
No. Creating an object URL lets the page display the selected file locally; uploading it requires separate application code.
Quick 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.

