The Old Way: Library Bloat
Every lightbox library adds weight:
- Lightbox2: 8KB + jQuery dependency
- GLightbox: 12KB
- Fancybox: 15KB
For showing images in a modal. In 2026.
The New Way: Native Dialog
HTML5 gave us <dialog>. It’s built into every modern browser. It does 90% of what lightbox libraries do, with zero dependencies.
flowchart LR
subgraph OLD["Old Approach"]
O1["Import Library"]
O2["Initialize Plugin"]
O3["Configure Options"]
O4["Handle Edge Cases"]
O1 --> O2 --> O3 --> O4
end
subgraph NEW["Native Dialog"]
N1["Write HTML"]
N2["10 Lines JS"]
N3["Done"]
N1 --> N2 --> N3
end
OLD --> |"8-15KB"| RESULT1["Working Lightbox"]
NEW --> |"0KB"| RESULT2["Working Lightbox"]
style NEW fill:#22c55e,color:#000
style RESULT2 fill:#22c55e,color:#000
The Complete Implementation
HTML
<dialog id="lightbox" class="lightbox">
<button class="lightbox-close" aria-label="Close">×</button>
<img id="lightbox-image" alt="" />
</dialog>
<!-- Thumbnails that open the lightbox -->
<img src="thumb1.jpg" data-full="full1.jpg" class="thumbnail" />
<img src="thumb2.jpg" data-full="full2.jpg" class="thumbnail" />
JavaScript (10 Lines)
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-image');
document.querySelectorAll('.thumbnail').forEach(img => {
img.addEventListener('click', () => {
lightboxImg.src = img.dataset.full;
lightbox.showModal();
});
});
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) lightbox.close();
});
CSS
.lightbox {
border: none;
padding: 0;
max-width: 95vw;
max-height: 95vh;
background: transparent;
}
.lightbox::backdrop {
background: rgba(0, 0, 0, 0.95);
}
.lightbox img {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
}
.lightbox-close {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
color: white;
font-size: 2rem;
cursor: pointer;
}
That’s it. Full lightbox. Zero dependencies.
What You Get for Free
1. Modal Behavior
showModal() vs show():
showModal(): Blocks interaction with page behind, shows backdropshow(): Non-modal, no backdrop, page still interactive
2. Backdrop Styling
The ::backdrop pseudo-element is built-in:
dialog::backdrop {
background: rgba(0, 0, 0, 0.95);
backdrop-filter: blur(5px); /* Bonus: blur effect */
}
3. Escape Key Handling
Pressing Escape closes the dialog automatically. No event listener needed.
4. Focus Trapping
When showModal() is called:
- Focus moves into dialog
- Tab key cycles within dialog only
- Focus returns to trigger element on close
This is accessibility you’d have to build manually with libraries.
5. Click-Outside-to-Close
One event listener:
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close();
});
Clicks on the backdrop (the dialog element itself) close it. Clicks on content don’t.
Browser Support
| Browser | Support |
|---|---|
| Chrome | 37+ (2014) |
| Firefox | 98+ (2022) |
| Safari | 15.4+ (2022) |
| Edge | 79+ (2020) |
95%+ global support. The remaining 5% get a fallback or polyfill.
Advanced: Animation
Add smooth open/close animations:
.lightbox {
opacity: 0;
transform: scale(0.95);
transition: opacity 0.2s, transform 0.2s;
}
.lightbox[open] {
opacity: 1;
transform: scale(1);
}
/* Backdrop animation */
.lightbox::backdrop {
opacity: 0;
transition: opacity 0.2s;
}
.lightbox[open]::backdrop {
opacity: 1;
}
When to Still Use a Library
Native dialog doesn’t cover everything:
- Image galleries with swipe - Need gesture handling
- Zoom and pan - Need touch gesture library
- Video lightboxes - Need video player integration
- Complex animations - Native transitions are limited
For basic image lightboxes? Native dialog wins.
Migration Guide
If you’re using Lightbox2:
// Before (Lightbox2)
<a href="full.jpg" data-lightbox="gallery">
<img src="thumb.jpg" />
</a>
// After (Native)
<img src="thumb.jpg" data-full="full.jpg" class="thumbnail" />
If you’re using GLightbox:
// Before
const lightbox = GLightbox({ selector: '.glightbox' });
// After
// Just the 10 lines above
Key Takeaways
- Native dialog handles modals - Built into HTML5
- Zero dependencies = zero bloat - No library to load
- Accessibility included - Focus trap, escape key, screen readers
- Backdrop styling is CSS -
::backdroppseudo-element - 10 lines of JS - That’s the entire implementation
Stop importing libraries for solved problems. The platform has caught up. Use it.
This pattern was adopted for a wedding invitation gallery where every kilobyte mattered for mobile users. Native dialog reduced the JavaScript bundle while improving accessibility.
