“Lightbox” effects have been an established UI pattern for a decade, but the vast majority of implementations have been framework-dependent. In previous articles I’ve shown how to create a Lightbox UI using only CSS, although that version lacked controls. By adding the <dialog> element and a dozen lines of vanilla , we can recreate a traditional lightbox perfectly:

The Markup

<nav id="thumbs">
	<a href="elephant.jpg"><img src="elephant-thumb.jpg" alt></a>
	<a href="taj-mahal.jpg"><img src="taj-mahal-thumb.jpg" alt></a>
	<a href="wise-man.jpg"><img src="wise-man-thumb.jpg" alt></a>
<dialog id="cover">
	<button id="closecover">Close</button>
	<img src="" alt>

The linked images follow the pattern used in my previous “Accessible Image Gallery with Progressive JavaScript” article; the large image inside the <dialog> element is a placeholder that we’ll change later using JavaScript. (alt values have been left blank for the sake of brevity).


@keyframes fadeToNearBlack {
	to { background: rgba(0,0,0,0.9); }
@keyframes goBig { 
	to { opacity: 1; } 
nav { 
	display: flex; 
nav a { 
	display: block; flex: 1; 
nav a img, 
dialog img { 
	width: 100%; height: auto; 
dialog { border: none; opacity: 0; }
dialog button { 
	border: none; background: none; font-size: 1.2rem; 
dialog[open] {
	animation: goBig 1s .4s forwards;
	width: 70%; margin: auto;
	position: absolute; max-width: 700px; 
dialog[open]::backdrop { 
	animation: fadeToNearBlack 1s forwards; 
.backdrop { 
	animation: fadeToNearBlack 1s forwards; 

The animations will fade in the <dialog> element; the <nav> links are displayed using to divide them evenly across the page. When visible, the <dialog> element is positioned using a variation on one of the established methods of centering elements when the height of the parent (the <body>, in this case) is unknown. (Vendor prefixes have been dropped for the purpose of clarity).

The JavaScript

The script goes at the end of the document; other browsers will require an additional polyfill to support the <dialog> element.

function showImage(e) {
	coverimage.setAttribute("src", this.getAttribute("href"));
	coverimage.setAttribute("alt", this.querySelector("img").getAttribute("alt"));
document.getElementById("closecover").onclick = function() { 
	coverimage.setAttribute("src", "");
var imglinks = document.getElementById("thumbs").getElementsByTagName('a'),
cover = document.getElementById("cover"),
coverimage = cover.getElementsByTagName("img")[0];
testdialog.setAttribute("open", "");
if (!testdialog.open) { 
for (var i=0; i<imglinks.length; i++) { 
	imglinks[i].onclick = showImage; 

The script attaches a function to each of the links; when the user clicks on one, it sets the src attribute of the large image in the <dialog> element to the value of the link and shows the modal window; the elements animate due to the CSS we applied earlier. Clicking on the altered <button> element closes the modal. Because the thumbnail images are links, they are also progressive and accessible.

Conclusion & Improvements

There are a few things that could be better; obviously, being able to drop the polyfill once all browsers support the <dialog> element would be good, and the element itself could be animated like the version in my CSS-only example. Centering the element perfectly across different platforms could also be improved: right now, things move around a little more than I would like. I’ll leave that and further enhancements for a future article.

Photographs by Richard Kardhordó, licensed under Creative Commons

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/bNdvym