Monsoon v0.2.0
Made by Alexandre Kohler Star

Media and files

Image viewer

Opens linked images in a full screen viewer with zoom (buttons, wheel, pinch, double click or double tap), pan, swipe, arrow keys, captions, a counter, thumbnails and a download button. It needs monsoon.js. Without it each link opens its image.

Media and files13 classesNeeds monsoon.jsLight and dark

Needs monsoon.js. The styles work on their own, and the behavior comes from the optional interactive layer: add <script src="https://cdn.jsdelivr.net/npm/monsooncss/dist/monsoon.js" defer></script> or import 'monsooncss/js'. See the interactive layer.

Example

Full

JavaScript API

Attribute

data-m-image-viewer on any element that holds the links; the viewer dialog is created on first open

Options

OptionDescription
data-m-image-viewer-selectorWhich children open the viewer, a[href], [data-src] by default. Captions come from data-caption, then the image alt.
data-m-image-viewer-loopfalse stops at the first and last image.
data-m-image-viewer-max-zoomLargest zoom, 5 by default.
data-m-image-viewer-thumbsfalse hides the thumbnail strip.

Methods

MethodDescription
Monsoon.imageViewer(el).open(index)Opens at an image.
Monsoon.imageViewer(el).close()Closes the viewer.
Monsoon.imageViewer(el).next() and prev()Moves between images.
Monsoon.imageViewer(el).zoom(value)Zooms the current image.

Events

EventDescription
monsoon:viewer-opendetail.index
monsoon:viewer-changedetail.index and detail.item (the link)
monsoon:viewer-closedetail.index

Keyboard

Left and Right change image, Home and End jump, plus, minus and 0 zoom, Escape closes.

Classes

13 classes. Click a name to copy it. Utilities on the same element always win over component defaults, so any of them can be adjusted with plain utility classes.

ClassTypeWhat it does
Base Base class of the image viewer. Not used in the example above. Sets fixed position, offsets, stacking order, hidden by default, grid tracks and width.
CSS
.viewer::backdrop {
  background: transparent;
}

.viewer[open] {
  display: grid;
  opacity: 1;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets flex display, alignment, gap and padding.
CSS
.viewer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 0.75rem 0.5rem 1.25rem;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets text color and text size.
CSS
.viewer-counter, .viewer-zoom {
  color: oklch(.97 0 0 / .72);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets text color, text size, width, text style and hidden by default.
CSS
.viewer-zoom {
  min-width: 3rem;
  text-align: center;
}

@media (max-width: 40rem) {
  .viewer-zoom {
    display: none;
  }
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets flex display, alignment and gap.
CSS
.viewer-tools {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets grid display, alignment, width, height, padding and border.
CSS
.viewer-btn:active {
  scale: 0.94;
}

.viewer-btn:hover {
  background: oklch(1 0 0 / .18);
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets grid display, alignment, height, padding, overflow and zoom-in cursor.
CSS
.viewer-stage[data-zoomed] {
  cursor: grab;
}

.viewer-stage[data-loading] .viewer-image {
  opacity: 0;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets width, height, corner radius, shadow, transition and animation.
CSS
.viewer-image {
  max-width: min(100%, 80rem);
  max-height: 100%;
  border-radius: var(--m-radius-md);
  box-shadow: 0 24px 64px -16px oklch(0 0 0 / .7);
  object-fit: contain;
  transition: transform var(--m-duration-slow) var(--m-ease), opacity var(--m-duration) var(--m-ease);
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .dropzone-area,
  .dropzone-glyph,
  .dropzone-item,
  .dropzone-errors,
  .dropzone-progress,
  .dropzone-progress::before,
  .cropper-grid,
  .cropper-handle::after,
  .avatar-upload-badge,
  .viewer,
  .viewer-image,
  .viewer-thumb,
  .gallery-item,
  .gallery-item img,
  .gallery-check,
  .gallery-check > input,
  .gallery-check > input::before,
  .compare-knob,
  .signature-hint,
  .signature-swatch {
    transition: none;
    animation: none;
  }
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets absolute position, offsets, width, height and transform.
CSS
.viewer-prev {
  left: 1rem;
}

@media (max-width: 40rem) {
  .viewer-prev, .viewer-next {
    top: auto;
    bottom: 5rem;
    translate: none;
  }
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets absolute position, offsets, width, height and transform.
CSS
.viewer-next {
  right: 1rem;
}

@media (max-width: 40rem) {
  .viewer-prev, .viewer-next {
    top: auto;
    bottom: 5rem;
    translate: none;
  }
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets width, margin, padding, text color, text size and text style.
CSS
.viewer-caption {
  max-width: 40rem;
  margin: 0 auto;
  padding: 0.5rem 1.25rem 0;
  color: oklch(.97 0 0 / .78);
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: center;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets flex display, alignment, gap, padding and overflow.
CSS
.viewer-thumbs {
  display: flex;
  justify-content: safe center;
  gap: 0.5rem;
  padding: 0.75rem 1rem 1rem;
  overflow-x: auto;
  scrollbar-width: none;
}
Modifier Modifier, combined with the viewer class. Not used in the example above. Sets border, alignment, width, height, padding and overflow.
CSS
.viewer-thumb:hover {
  opacity: 0.85;
}

.viewer-thumb > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Usage

The styles ship inside the same stylesheet as the utilities, and the behavior comes from the optional monsoon.js script, which enhances any element with a matching data-m attribute, including markup added later.

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss">
<script src="https://cdn.jsdelivr.net/npm/monsooncss/dist/monsoon.js" defer></script>

<!-- Image viewer: start from the base class, then add modifiers -->
<div class="grid max-w-md grid-cols-4 gap-2" data-m-image-viewer><a href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='800'%20viewBox='0%200%201200%20800'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23fdba74'/%3E%3Cstop%20offset='1'%20stop-color='%23f472b6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='800'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='272'%20r='88'%20fill='%23fff7d6'/%3E%3Cpath%20d='M0%20496L240%20336L456%20464L720%20288L1200%20528V800H0Z'%20fill='%237c3aed'/%3E%3Cpath%20d='M0%20592L336%20448L624%20576L936%20480L1200%20608V800H0Z'%20fill='%233b0764'/%3E%3Crect%20y='672'%20width='1200'%20height='128'%20fill='%231e1b4b'%20opacity='.85'/%3E%3C/svg%3E" data-caption="Lake Aurel, just after sunset" class="block overflow-hidden rounded-lg"><img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='800'%20viewBox='0%200%201200%20800'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23fdba74'/%3E%3Cstop%20offset='1'%20stop-color='%23f472b6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='800'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='272'%20r='88'%20fill='%23fff7d6'/%3E%3Cpath%20d='M0%20496L240%20336L456%20464L720%20288L1200%20528V800H0Z'%20fill='%237c3aed'/%3E%3Cpath%20d='M0%20592L336%20448L624%20576L936%20480L1200%20608V800H0Z'%20fill='%233b0764'/%3E%3Crect%20y='672'%20width='1200'%20height='128'%20fill='%231e1b4b'%20opacity='.85'/%3E%3C/svg%3E" alt="Violet ridges at dusk" class="aspect-square w-full object-cover"></a><a href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='800'%20viewBox='0%200%201200%20800'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%237dd3fc'/%3E%3Cstop%20offset='1'%20stop-color='%23e0f2fe'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='800'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='272'%20r='88'%20fill='%23fef9c3'/%3E%3Cpath%20d='M0%20496L240%20336L456%20464L720%20288L1200%20528V800H0Z'%20fill='%230ea5e9'/%3E%3Cpath%20d='M0%20592L336%20448L624%20576L936%20480L1200%20608V800H0Z'%20fill='%230369a1'/%3E%3Crect%20y='672'%20width='1200'%20height='128'%20fill='%230c4a6e'%20opacity='.85'/%3E%3C/svg%3E" data-caption="Morning over the bay" class="block overflow-hidden rounded-lg"><img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='800'%20viewBox='0%200%201200%20800'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%237dd3fc'/%3E%3Cstop%20offset='1'%20stop-color='%23e0f2fe'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='800'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='272'%20r='88'%20fill='%23fef9c3'/%3E%3Cpath%20d='M0%20496L240%20336L456%20464L720%20288L1200%20528V800H0Z'%20fill='%230ea5e9'/%3E%3Cpath%20d='M0%20592L336%20448L624%20576L936%20480L1200%20608V800H0Z'%20fill='%230369a1'/%3E%3Crect%20y='672'%20width='1200'%20height='128'%20fill='%230c4a6e'%20opacity='.85'/%3E%3C/svg%3E" alt="Blue bay under a pale sky" class="aspect-square w-full object-cover"></a><a href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='900'%20viewBox='0%200%201200%20900'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23bbf7d0'/%3E%3Cstop%20offset='1'%20stop-color='%23fef3c7'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='900'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='306'%20r='99'%20fill='%23fde68a'/%3E%3Cpath%20d='M0%20558L240%20378L456%20522L720%20324L1200%20594V900H0Z'%20fill='%2315803d'/%3E%3Cpath%20d='M0%20666L336%20504L624%20648L936%20540L1200%20684V900H0Z'%20fill='%2314532d'/%3E%3Crect%20y='756'%20width='1200'%20height='144'%20fill='%23052e16'%20opacity='.85'/%3E%3C/svg%3E" data-caption="Pine valley in late summer" class="block overflow-hidden rounded-lg"><img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1200'%20height='900'%20viewBox='0%200%201200%20900'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23bbf7d0'/%3E%3Cstop%20offset='1'%20stop-color='%23fef3c7'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='1200'%20height='900'%20fill='url(%23s)'/%3E%3Ccircle%20cx='840'%20cy='306'%20r='99'%20fill='%23fde68a'/%3E%3Cpath%20d='M0%20558L240%20378L456%20522L720%20324L1200%20594V900H0Z'%20fill='%2315803d'/%3E%3Cpath%20d='M0%20666L336%20504L624%20648L936%20540L1200%20684V900H0Z'%20fill='%2314532d'/%3E%3Crect%20y='756'%20width='1200'%20height='144'%20fill='%23052e16'%20opacity='.85'/%3E%3C/svg%3E" alt="Green hills and a golden sky" class="aspect-square w-full object-cover"></a><a href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='900'%20height='1200'%20viewBox='0%200%20900%201200'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23fde68a'/%3E%3Cstop%20offset='1'%20stop-color='%23fb923c'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='900'%20height='1200'%20fill='url(%23s)'/%3E%3Ccircle%20cx='630'%20cy='408'%20r='99'%20fill='%23fffbeb'/%3E%3Cpath%20d='M0%20744L180%20504L342%20696L540%20432L900%20792V1200H0Z'%20fill='%23c2410c'/%3E%3Cpath%20d='M0%20888L252%20672L468%20864L702%20720L900%20912V1200H0Z'%20fill='%237c2d12'/%3E%3Crect%20y='1008'%20width='900'%20height='192'%20fill='%23431407'%20opacity='.85'/%3E%3C/svg%3E" data-caption="Dunes at noon, shot in portrait" class="block overflow-hidden rounded-lg"><img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='900'%20height='1200'%20viewBox='0%200%20900%201200'%3E%3Cdefs%3E%3ClinearGradient%20id='s'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23fde68a'/%3E%3Cstop%20offset='1'%20stop-color='%23fb923c'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='900'%20height='1200'%20fill='url(%23s)'/%3E%3Ccircle%20cx='630'%20cy='408'%20r='99'%20fill='%23fffbeb'/%3E%3Cpath%20d='M0%20744L180%20504L342%20696L540%20432L900%20792V1200H0Z'%20fill='%23c2410c'/%3E%3Cpath%20d='M0%20888L252%20672L468%20864L702%20720L900%20912V1200H0Z'%20fill='%237c2d12'/%3E%3Crect%20y='1008'%20width='900'%20height='192'%20fill='%23431407'%20opacity='.85'/%3E%3C/svg%3E" alt="Orange dunes in tall format" class="aspect-square w-full object-cover"></a></div>

Theme it with --seed on any ancestor, for example style="--seed: #0d9488", and force a scheme with class="dark" or class="light" on a wrapper.

Accessibility

  • Give meaningful images an alt text and decorative ones alt="". Initials avatars should expose the full name to screen readers.
  • Every interactive part shows a visible focus ring with :focus-visible, and tone colors pair with -foreground tokens that keep text at 4.5:1 or better in light and dark mode.

No recent searches

Try , , or .

Recent

Loading the search index

No results for ""

Check the spelling, or search a class name such as px-4.