Overlays suck, but if you need one, consider using Parvus. Parvus is an open source, dependency free image lightbox with the goal of being accessible.
- CSS:
dist/css/parvus.min.css(minified) ordist/css/parvus.css(un-minified)
- JavaScript:
dist/js/parvus.min.js(minified) ordist/js/parvus.js(un-minified)
Link the .css and .js files in your HTML:
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport" content="width=device-width, initial-scale=1"><title>Page title</title><!-- CSS --><linkhref="path/to/parvus.min.css" rel="stylesheet"></head><body><!-- HTML content --><!-- JS --><scriptsrc="path/to/parvus.min.js"></script></body></html>You can also install Parvus using npm or yarn:
npm install parvusor
yarn add parvusAfter installation, import Parvus into your JavaScript codebase:
importParvusfrom'parvus'Be sure to include the corresponding SCSS or CSS file.
Link a thumbnail image with the class lightbox to a larger image:
<ahref="path/to/image.jpg" class="lightbox"><imgsrc="path/to/thumbnail.jpg" alt=""></a>Initialize the script:
constprvs=newParvus()There are three ways to add a caption to an image:
You can add an ID to your caption element and reference it from the trigger element using the data-caption-id attribute.
<figure><ahref="path/to/image.jpg" class="lightbox" data-caption-id="caption-1"><imgsrc="path/to/thumbnail.jpg" alt=""></a><figcaptionid="caption-1">
I'm a caption, and I live outside the link.
</figcaption></figure>You can add a data-caption attribute directly to the trigger element.
<ahref="path/to/image.jpg" class="lightbox" data-caption="I'm a simple caption"><imgsrc="path/to/thumbnail.jpg" alt=""></a>Alternatively, set the option captionsSelector to select a caption from a child element's innerHTML.
<ahref="path/to/image.jpg" class="lightbox"><figureclass="figure"><imgsrc="path/to/thumbnail.jpg" alt=""><figcaptionclass="figure__caption">
I'm a caption inside a child element
</figcaption></figure></a>constprvs=newParvus({captionsSelector: '.figure__caption',})There are three ways to add copyright information to an image:
You can add an ID to your copyright element and reference it from the trigger element using the data-copyright-id attribute.
<ahref="path/to/image.jpg" class="lightbox" data-copyright-id="copyright-1"><imgsrc="path/to/thumbnail.jpg" alt=""></a><smallid="copyright-1" hidden>
© 2026 Photographer Name
</small>You can add a data-copyright attribute directly to the trigger element.
<ahref="path/to/image.jpg" class="lightbox" data-copyright="© 2026 Photographer Name"><imgsrc="path/to/thumbnail.jpg" alt=""></a>Alternatively, set the option copyrightSelector to select a copyright from a child element's innerHTML.
<ahref="path/to/image.jpg" class="lightbox"><figureclass="figure"><imgsrc="path/to/thumbnail.jpg" alt=""><smallclass="figure__copyright">
© 2026 Photographer Name
</small></figure></a>constprvs=newParvus({copyrightSelector: '.figure__copyright',})To group related images into a set, add a data-group attribute:
<ahref="path/to/image.jpg" class="lightbox" data-group="Berlin"><imgsrc="path/to/thumbnail.jpg" alt=""></a><ahref="path/to/image_2.jpg" class="lightbox" data-group="Berlin"><imgsrc="path/to/thumbnail_2.jpg" alt=""></a>
//...
<ahref="path/to/image_8.jpg" class="lightbox" data-group="Kassel"><imgsrc="path/to/thumbnail_8.jpg" alt=""></a>Alternatively, set the option gallerySelector to group all images with a specific class within a selector:
<divclass="gallery"><ahref="path/to/image.jpg" class="lightbox"><imgsrc="path/to/thumbnail.jpg" alt=""></a><ahref="path/to/image_2.jpg" class="lightbox"><imgsrc="path/to/thumbnail_2.jpg" alt=""></a>
// ...
</div>constprvs=newParvus({gallerySelector: '.gallery',})Specify different image sources and sizes using the data-srcset and data-sizes attributes:
<ahref="path/to/image.jpg" class="lightbox"
data-srcset="path/to/small.jpg 700w, path/to/medium.jpg 1000w, path/to/large.jpg 1200w"
data-sizes="(max-width: 75em) 100vw, 75em"
><imgsrc="path/to/thumbnail.jpg" alt=""></a>Import the language module and set it as an option for localization:
importdefrom'parvus/src/l10n/de'constprvs=newParvus({l10n: de})Customize Parvus by passing an options object when initializing:
constprvs=newParvus({// Clicking outside does not close ParvusdocClose: false})Available options include:
{// Selector for elements that trigger Parvusselector: '.lightbox',// Selector for a group of elements combined as a gallery, overrides the `data-group` attribute.gallerySelector: null,// Display zoom indicatorzoomIndicator: true,// Display captions if availablecaptions: true,// Selector for the element where the caption is displayed; use "self" for the `a` tag itself.captionsSelector: 'self',// Attribute to get the caption fromcaptionsAttribute: 'data-caption',// Display copyright if availablecopyright: true,// Selector for the element where the copyright is displayed; use "self" for the `a` tag itself.copyrightSelector: 'self',// Attribute to get the copyright fromcopyrightAttribute: 'data-copyright',// Clicking outside closes ParvusdocClose: true,// Close Parvus by swiping up/downswipeClose: true,// Accept mouse events like touch events (click and drag to change slides)simulateTouch: true,// Touch dragging threshold in pixelsthreshold: 100,// Hide browser scrollbarhideScrollbar: true,// IconslightboxIndicatorIcon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/></svg>',previousButtonIcon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="15 6 9 12 15 18" /></svg>',nextButtonIcon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="9 6 15 12 9 18" /></svg>',closeButtonIcon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M18 6L6 18M6 6l12 12"/></svg>',// Localization of stringsl10n: en}Parvus provides the following API functions:
| Function | Description |
|---|---|
open(element) | Open the specified element (DOM element) |
close() | Close Parvus |
previous() | Show the previous image |
next() | Show the next image |
select(index) | Select a slide with the specified index (integer) |
add(element) | Add the specified element (DOM element) |
remove(element) | Remove the specified element (DOM element) |
destroy() | Destroy Parvus |
isOpen() | Check if Parvus is currently open |
currentIndex() | Get the index of the currently displayed slide |
use(plugin, options) | Register a plugin |
addHook(hookName, callback) | Add a hook callback |
getPlugins() | Get list of registered plugins |
Bind and unbind events using the .on() and .off() methods:
constprvs=newParvus()constlistener=()=>{console.log('eventName happened')}// Bind event listenerprvs.on(eventName,listener)// Unbind event listenerprvs.off(eventName,listener)Available events:
| eventName | Description |
|---|---|
open | Triggered after Parvus has opened |
select | Triggered when a slide is selected |
close | Triggered after Parvus has closed |
destroy | Triggered after Parvus has destroyed |
Parvus supports a plugin system that allows you to extend its functionality.
To use a plugin, call the .use() method after initialization:
importParvusfrom'parvus'importMyPluginfrom'./my-plugin.js'constprvs=newParvus()// Register pluginprvs.use(MyPlugin,{// Plugin-specific optionsoption1: 'value1',option2: 'value2'})A plugin is an object with a name and an install function:
constMyPlugin={name: 'MyPlugin',install(parvus,options={}){// Plugin initialization codeconsole.log('Plugin installed with options: ',options)}}exportdefaultMyPluginPlugins can hook into various lifecycle events:
| Hook Name | When Triggered | Provided Data |
|---|---|---|
afterInit | After lightbox DOM is created (once) | { state } |
afterOpen | After lightbox opens | { element, state } |
afterClose | After lightbox closes | { state } |
slideChange | When slide changes | { index, oldIndex, state } |
Example using hooks:
constMyPlugin={name: 'MyPlugin',install(parvus,options){// Add a custom button on initparvus.addHook('afterInit',({ state })=>{constbtn=document.createElement('button')btn.classList.add('parvus__btn')btn.classList.add('parvus__btn--my-plugin')btn.textContent='Custom'btn.type='button'// Add to controls as first elementif(state.controls){state.controls.prepend(btn)}})// Track slide changesparvus.addHook('slideChange',({ index, oldIndex })=>{console.log(`Changed from slide ${oldIndex} to ${index}`)})}}Parvus is supported on the latest versions of the following browsers:
- Chrome
- Edge
- Firefox
- Safari
