Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha
API navigation

API

Lightbox

A fullscreen gallery dialog built on the carousel.

Installation

pnpm dlx zazz-ui add lightbox
npx zazz-ui add lightbox
yarn dlx zazz-ui add lightbox
bunx zazz-ui add lightbox

Also adds fields, kbd, button, dialog, carousel, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.

Examples

Mountain landscape
Valley with sunlight
Forest path
Waterfall in forest
Lake at sunset
Foggy forest
<ui-lightbox
  style="
    --mx: auto;
    --max-w: var(--article-sm);
  "
>
  <div data-lightbox-slot="gallery">
    <ui-carousel data-lightbox-slot="stage" data-carousel-loop="true">
      <div data-carousel-slot="viewport">
        <div data-carousel-slot="container">
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop 800w
                "
                alt="Mountain landscape"
              />
            </figure>
          </button>
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&h=600&fit=crop 800w
                "
                alt="Valley with sunlight"
              />
            </figure>
          </button>
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800&h=600&fit=crop 800w
                "
                alt="Forest path"
              />
            </figure>
          </button>
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=800&h=600&fit=crop 800w
                "
                alt="Waterfall in forest"
              />
            </figure>
          </button>
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=800&h=600&fit=crop 800w
                "
                alt="Lake at sunset"
              />
            </figure>
          </button>
          <button
            data-lightbox-slot="slide"
            data-carousel-slot="slide"
            type="button"
            command="show-modal"
            commandfor="lightbox-example-1"
          >
            <figure data-lightbox-slot="content">
              <img
                sizes="(max-width: 40rem) 100vw, 800px"
                src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=600&fit=crop"
                srcset="
                  https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=456&h=342&fit=crop 456w,
                  https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=600&fit=crop 800w
                "
                alt="Foggy forest"
              />
            </figure>
          </button>
        </div>
      </div>
      <div data-lightbox-slot="thumbs" data-carousel-slot="thumbs" data-carousel-loop="true">
        <button
          data-ui="button"
          data-lightbox-slot="thumbs-prev"
          data-carousel-slot="prev"
          data-button-size="icon-sm"
          data-button-variant="ghost"
          type="button"
          aria-label="Previous image"
          style="
            --text: var(--color-muted-foreground);
            --text--hover: var(--color-foreground);
          "
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 256 256"
            aria-hidden="true"
            style="--size: 4"
          >
            <rect width="256" height="256" fill="none" />
            <polyline
              points="160 208 80 128 160 48"
              fill="none"
              stroke="currentColor"
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="14"
            />
          </svg>
        </button>
        <div data-carousel-slot="viewport">
          <div data-carousel-slot="container">
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 1"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=456&h=342&fit=crop 456w
                  "
                  alt="Mountain landscape"
                />
              </figure>
            </button>
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 2"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=456&h=342&fit=crop 456w
                  "
                  alt="Valley with sunlight"
                />
              </figure>
            </button>
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 3"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=456&h=342&fit=crop 456w
                  "
                  alt="Forest path"
                />
              </figure>
            </button>
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 4"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=456&h=342&fit=crop 456w
                  "
                  alt="Waterfall in forest"
                />
              </figure>
            </button>
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 5"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=456&h=342&fit=crop 456w
                  "
                  alt="Lake at sunset"
                />
              </figure>
            </button>
            <button
              type="button"
              data-lightbox-slot="thumb"
              data-carousel-slot="slide"
              aria-label="Show image 6"
            >
              <figure data-lightbox-slot="thumb-content">
                <img
                  sizes="auto"
                  loading="lazy"
                  width="228"
                  height="171"
                  src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=228&h=171&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=228&h=171&fit=crop 228w,
                    https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=456&h=342&fit=crop 456w
                  "
                  alt="Foggy forest"
                />
              </figure>
            </button>
          </div>
        </div>
        <button
          data-ui="button"
          data-lightbox-slot="thumbs-next"
          data-carousel-slot="next"
          data-button-size="icon-sm"
          data-button-variant="ghost"
          type="button"
          aria-label="Next image"
          style="
            --text: var(--color-muted-foreground);
            --text--hover: var(--color-foreground);
          "
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 256 256"
            aria-hidden="true"
            style="--size: 4"
          >
            <rect width="256" height="256" fill="none" />
            <polyline
              points="96 48 176 128 96 208"
              fill="none"
              stroke="currentColor"
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="14"
            />
          </svg>
        </button>
      </div>
    </ui-carousel>
  </div>

  <dialog
    id="lightbox-example-1"
    data-ui="dialog theme-dark"
    data-lightbox-slot="dialog"
    data-dialog-size="screen"
    closedby="any"
  >
    <div data-dialog-slot="content">
      <ui-carousel data-carousel-loop="true">
        <div data-carousel-slot="viewport">
          <div data-carousel-slot="container">
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop 800w
                  "
                  alt="Mountain landscape"
                />
              </figure>
            </div>
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&h=600&fit=crop 800w
                  "
                  alt="Valley with sunlight"
                />
              </figure>
            </div>
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800&h=600&fit=crop 800w
                  "
                  alt="Forest path"
                />
              </figure>
            </div>
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=800&h=600&fit=crop 800w
                  "
                  alt="Waterfall in forest"
                />
              </figure>
            </div>
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=800&h=600&fit=crop 800w
                  "
                  alt="Lake at sunset"
                />
              </figure>
            </div>
            <div data-lightbox-slot="slide" data-carousel-slot="slide">
              <figure data-lightbox-slot="content">
                <img
                  sizes="(max-width: 40rem) 100vw, 800px"
                  src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=600&fit=crop"
                  srcset="
                    https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=456&h=342&fit=crop 456w,
                    https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=600&fit=crop 800w
                  "
                  alt="Foggy forest"
                />
              </figure>
            </div>
          </div>
        </div>
        <button
          data-ui="button"
          data-lightbox-slot="prev"
          data-carousel-slot="prev"
          data-button-size="icon"
          data-button-variant="ghost"
          type="button"
          aria-label="Previous image"
          style="
            --w: auto;
            --h: auto;
            --text: var(--color-muted-foreground);
            --text--hover: var(--color-foreground);
          "
        >
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" style="--size: 6">
            <rect width="256" height="256" fill="none" />
            <polyline
              points="160 208 80 128 160 48"
              fill="none"
              stroke="currentColor"
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="14"
            />
          </svg>
        </button>
        <button
          data-ui="button"
          data-lightbox-slot="next"
          data-carousel-slot="next"
          data-button-size="icon"
          data-button-variant="ghost"
          type="button"
          aria-label="Next image"
          style="
            --w: auto;
            --h: auto;
            --text: var(--color-muted-foreground);
            --text--hover: var(--color-foreground);
          "
        >
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" style="--size: 6">
            <rect width="256" height="256" fill="none" />
            <polyline
              points="96 48 176 128 96 208"
              fill="none"
              stroke="currentColor"
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="14"
            />
          </svg>
        </button>
        <div data-lightbox-slot="thumbs" data-carousel-slot="thumbs">
          <div data-carousel-slot="viewport">
            <div data-carousel-slot="container">
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 1"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=456&h=342&fit=crop 456w
                    "
                    alt="Mountain landscape"
                  />
                </figure>
              </button>
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 2"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=456&h=342&fit=crop 456w
                    "
                    alt="Valley with sunlight"
                  />
                </figure>
              </button>
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 3"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=456&h=342&fit=crop 456w
                    "
                    alt="Forest path"
                  />
                </figure>
              </button>
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 4"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=456&h=342&fit=crop 456w
                    "
                    alt="Waterfall in forest"
                  />
                </figure>
              </button>
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 5"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=456&h=342&fit=crop 456w
                    "
                    alt="Lake at sunset"
                  />
                </figure>
              </button>
              <button
                type="button"
                data-lightbox-slot="thumb"
                data-carousel-slot="slide"
                aria-label="Show image 6"
              >
                <figure data-lightbox-slot="thumb-content">
                  <img
                    sizes="auto"
                    loading="lazy"
                    width="228"
                    height="171"
                    src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=228&h=171&fit=crop"
                    srcset="
                      https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=228&h=171&fit=crop 228w,
                      https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=456&h=342&fit=crop 456w
                    "
                    alt="Foggy forest"
                  />
                </figure>
              </button>
            </div>
          </div>
        </div>
      </ui-carousel>
    </div>
    <button
      data-ui="button"
      data-lightbox-slot="close"
      data-button-size="icon"
      data-button-variant="ghost"
      type="button"
      commandfor="lightbox-example-1"
      command="close"
      aria-label="Close lightbox"
    >
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
        <rect width="256" height="256" fill="none" />
        <line
          x1="200"
          y1="56"
          x2="56"
          y2="200"
          stroke="currentColor"
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="16"
        />
        <line
          x1="200"
          y1="200"
          x2="56"
          y2="56"
          stroke="currentColor"
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="16"
        />
      </svg>
    </button>
  </dialog>
</ui-lightbox>
/**
 * lightbox.css — Lightbox gallery (ui-lightbox | [data-ui~="lightbox"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, dialog.css, carousel.css, button.css
 * @uses       command="show-modal" — Invoker Commands open fullscreen dialog (no JS)
 * @uses       @container — thumb column count at narrow widths
 * @uses       @starting-style — dialog slide scale/opacity on snap
 * @uses       Embla sync — requires zazz/scripts/embla.js for carousel behaviour
 * @tokens     --ui-lightbox-* (@layer variables)
 */

@layer variables {
  :root {
    --ui-lightbox-aspect: 3 / 2;
    --ui-lightbox-object-fit: cover;
    --ui-lightbox-slide-rounded: var(--radius-lg);
    --ui-lightbox-thumb-rounded: var(--radius-md);
    --ui-lightbox-thumb-size: calc(var(--spacing) * 16);
    --ui-lightbox-thumb-opacity: 0.5;
    --ui-lightbox-thumb-opacity--active: 1;
    --ui-lightbox-thumb-columns: 6;
    --ui-lightbox-thumb-gap: calc(var(--spacing) * 1.5);
    --ui-lightbox-img-border: 1px solid var(--color-faded);

    /* Reveal transitions/animations */
    --ui-lightbox-slide-opacity--start: 0;
    --ui-lightbox-slide-opacity--end: 1;
    --ui-lightbox-slide-scale--start: 0.9;
    --ui-lightbox-slide-scale--end: 1;
    --ui-lightbox-img-scale: 1;
    --ui-lightbox-img-opacity: 1;
    --ui-lightbox-img-scale--hover: 1.03;
    --ui-lightbox-img-filter--hover: brightness(0.8);
    --ui-lightbox-img-scale--active: 1;
    --ui-lightbox-img-filter--active: brightness(0.5);
    --ui-lightbox-img-transition-property: transform, filter;
    --ui-lightbox-img-transition-duration: calc(var(--default-transition-duration) * 3);
    --ui-lightbox-img-transition-timing-function: var(--default-transition-timing-function);
    --ui-lightbox-img-transition-delay: 0ms;
  }
}

@layer ui.components {
  /* ===========================================================================
  LIGHTBOX — image gallery with fullscreen dialog slideshow
  - Inline gallery: main Embla carousel + linked thumb strip.
  - Gallery slides are <button command="show-modal"> — opens dialog at current index.
  - Dialog repeats main carousel + thumbs with prev/next navigation.
  - No JavaScript required for open/close — uses Invoker Commands API.
  =========================================================================== */

  /* ---------------------------------------------------------------------------
  GALLERY — :where(ui-lightbox, [data-ui~="lightbox"])
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) {
    display: flex;
    flex-direction: column;
  }

  @container (width <= 40rem) {
    :where(ui-lightbox, [data-ui~="lightbox"]) {
      --ui-lightbox-thumb-columns: 4;
    }
  }

  /* ---------------------------------------------------------------------------
  INLINE GALLERY — [data-lightbox-slot~="gallery"] / [data-lightbox-slot~="stage"]
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="gallery"] {
    inline-size: 100%;
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="stage"] {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: calc(var(--ui-lightbox-thumb-gap) * 2);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    > [data-carousel-slot~="viewport"] {
    border-radius: var(--ui-lightbox-slide-rounded);
    background: var(--color-card);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    > [data-carousel-slot~="viewport"]
    > [data-carousel-slot~="container"] {
    border-radius: var(--ui-lightbox-slide-rounded);
    margin-inline-start: calc(var(--space-sm) * -1);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="slide"] {
    appearance: none;
    border: none;
    background: none;
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: 100%;
    cursor: zoom-in;
    overflow: clip;
    padding-inline-start: var(--space-sm);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="content"] {
    --_ring-width: 0px;

    aspect-ratio: var(--ui-lightbox-aspect);
    inline-size: 100%;
    block-size: 100%;
    overflow: clip;
    position: relative;
    border-radius: var(--ui-lightbox-slide-rounded);
    border: var(--ui-lightbox-img-border);

    /* transparent twin of the inset ring for forced-colors / high-contrast modes */
    outline: var(--outline-width) var(--outline-style) transparent;
    outline-offset: calc(var(--outline-width) * -1);
  }

  /* The inset ring paints on an overlay so it sits above the slide image (an
     inset box-shadow on the figure itself would hide behind the img); the
     ring width animates from 0 so the ring grows in smoothly on focus. */
  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="content"]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    box-shadow: inset 0 0 0 var(--_ring-width) var(--color-ring);
    transition: box-shadow var(--default-transition-duration)
      var(--default-transition-timing-function);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="slide"]:focus-visible {
    outline: none;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="slide"]:focus-visible
    [data-lightbox-slot~="content"] {
    --_ring-width: var(--ring-width);

    outline-color: transparent;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="slide"]
    img {
    aspect-ratio: var(--ui-lightbox-aspect);
    inline-size: 100%;
    block-size: 100%;
    object-fit: var(--ui-lightbox-object-fit);
    user-select: none;
    -webkit-user-drag: none;
    pointer-events: none;
    transition-property: var(--ui-lightbox-img-transition-property);
    transition-duration: var(--ui-lightbox-img-transition-duration);
    transition-timing-function: var(--ui-lightbox-img-transition-timing-function);
    transition-delay: var(--ui-lightbox-img-transition-delay);
    transform: scale(var(--ui-lightbox-img-scale));
    opacity: var(--ui-lightbox-img-opacity);
  }

  @media (hover: hover) {
    :where(ui-lightbox, [data-ui~="lightbox"])
      [data-lightbox-slot~="stage"]
      [data-lightbox-slot~="slide"]:hover
      img {
      transform: scale(var(--ui-lightbox-img-scale--hover));
      filter: var(--ui-lightbox-img-filter--hover);
    }
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="slide"]:active
    img {
    transform: scale(var(--ui-lightbox-img-scale--active));
    filter: var(--ui-lightbox-img-filter--active);
  }

  /* ---------------------------------------------------------------------------
  thumbs — [data-lightbox-slot~="thumbs"] / [data-lightbox-slot~="thumb"]
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumbs"] {
    position: relative;
    z-index: 2;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="thumbs"] {
    display: flex;
    align-items: center;
    gap: var(--ui-lightbox-thumb-gap);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="thumbs"]
    [data-carousel-slot~="viewport"] {
    flex: 1;
    min-inline-size: 0;
    border-radius: var(--ui-lightbox-thumb-rounded);
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumbs-prev"],
  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumbs-next"] {
    flex-shrink: 0;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="thumbs"]
    [data-carousel-slot~="container"] {
    gap: var(--ui-lightbox-thumb-gap);
  }

  /* Gallery thumbs: fill track evenly, centered, max 6 columns */
  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="thumbs"]
    [data-carousel-slot~="container"] {
    justify-content: start;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="stage"]
    [data-lightbox-slot~="thumb"] {
    max-inline-size: calc(
      (100% - (var(--ui-lightbox-thumb-gap) * (var(--ui-lightbox-thumb-columns) - 1))) /
        var(--ui-lightbox-thumb-columns)
    );
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb-content"] {
    --_ring-width: 0px;
    --_ring-fill: var(--color-ring);

    appearance: none;
    cursor: pointer;
    position: relative;
    border: var(--ui-lightbox-img-border);
    border-radius: var(--ui-lightbox-thumb-rounded);
    overflow: clip;

    /* transparent twin of the inset ring for forced-colors / high-contrast modes */
    outline: var(--outline-width) var(--outline-style) transparent;
    outline-offset: calc(var(--outline-width) * -1);
  }

  /* The inset ring paints on an overlay so it sits above the thumb image (an
     inset box-shadow on the button itself would hide behind the img). */
  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb-content"]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    box-shadow: inset 0 0 0 var(--_ring-width) var(--_ring-fill);
    transition: box-shadow var(--default-transition-duration)
      var(--default-transition-timing-function);
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb"] img {
    opacity: var(--ui-lightbox-thumb-opacity);
    transition:
      opacity var(--default-transition-duration) var(--default-transition-timing-function),
      transform var(--default-transition-duration) var(--default-transition-timing-function);
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  @media (hover: hover) {
    :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb"]:hover img {
      opacity: var(--ui-lightbox-thumb-opacity--active);
    }
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="thumb"][data-carousel-state~="active"]
    img {
    transform: scale(1.1);
    opacity: var(--ui-lightbox-thumb-opacity--active);
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb"]:focus-visible {
    outline: none;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="thumb"]:focus-visible
    [data-lightbox-slot~="thumb-content"] {
    --_ring-width: var(--ring-width);

    outline-color: transparent;
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="thumb"]:focus-visible img {
    transform: scale(1);
  }

  /* ---------------------------------------------------------------------------
  DIALOG — [data-lightbox-slot~="dialog"] (fullscreen dark overlay)
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="dialog"] {
    --ui-lightbox-thumb-rounded: var(--radius-sm);
    --ui-dialog-bg: transparent;
    --ui-dialog-border: none;

    overflow: clip;
    pointer-events: none;
    outline: none;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="thumb"]
    img {
    border-radius: var(--ui-lightbox-thumb-rounded);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-dialog-slot~="content"] {
    grid-template-rows: 1fr;
    max-block-size: 100svh;
    block-size: 100svh;
    overflow: clip;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    :where(ui-carousel, [data-ui~="carousel"]) {
    display: grid;
    grid-template-rows: 1fr auto;
    block-size: 100%;
    max-block-size: 100svh;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-carousel-slot~="container"] {
    block-size: 100%;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-carousel-slot~="viewport"] {
    outline: none;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    :where(ui-carousel, [data-ui~="carousel"])
    > [data-carousel-slot~="viewport"]:first-of-type {
    min-block-size: 0;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="thumbs"] {
    padding-block: var(--space-sm);
    pointer-events: auto;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="thumbs"]
    [data-carousel-slot~="container"] {
    justify-content: center;
    padding-block: var(--space-xs);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="thumb"] {
    flex: 0 0 var(--ui-lightbox-thumb-size);
    inline-size: var(--ui-lightbox-thumb-size);
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="thumb"]:focus-visible
    [data-lightbox-slot~="thumb-content"] {
    --_ring-fill: var(--color-foreground);
  }

  /* ---------------------------------------------------------------------------
  SLIDE — [data-lightbox-slot~="slide"] (dialog full-size slides)
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="slide"] {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 0;
    min-inline-size: 0;
    padding: clamp(var(--space-sm), 4vi, var(--space-lg));
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="content"] {
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: 100%;
    inline-size: 100%;
    min-block-size: 0;
    min-inline-size: 0;
    max-block-size: 100%;
    max-inline-size: 100%;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="slide"]
    img {
    --ui-lightbox-thumb-rounded: var(--radius-xs);

    border-radius: var(--ui-lightbox-slide-rounded);
    border: var(--ui-lightbox-img-border);
    overflow: clip;
    inline-size: 100%;
    block-size: 100%;
    max-block-size: fit-content;
    max-inline-size: fit-content;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
    transition:
      opacity var(--default-transition-duration) var(--default-transition-timing-function),
      transform var(--default-transition-duration) var(--default-transition-timing-function);
    opacity: var(--ui-lightbox-slide-opacity--start);
    transform: scale(var(--ui-lightbox-slide-scale--start));
    pointer-events: auto;
  }

  :where(ui-lightbox, [data-ui~="lightbox"])
    [data-lightbox-slot~="dialog"]
    [data-lightbox-slot~="slide"][data-carousel-state~="in-view"][data-carousel-state~="snapped"]
    img {
    opacity: var(--ui-lightbox-slide-opacity--end);
    transform: scale(var(--ui-lightbox-slide-scale--end));

    @starting-style {
      opacity: 0;
      transform: scale(var(--ui-lightbox-slide-scale--start));
    }
  }

  /* ---------------------------------------------------------------------------
  NAV BUTTONS — [data-lightbox-slot~="prev"] / [data-lightbox-slot~="next"] (icon-size [data-ui~="button"])
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="prev"],
  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="next"] {
    --ui-button-ring-color: var(--color-foreground);
    --ring-opacity: 80%;

    pointer-events: auto;
    position: absolute;
    inset-block-start: 50%;
    transform: translateY(-50%);
    z-index: 50;
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="prev"] {
    inset-inline-start: var(--space-md);
  }

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="next"] {
    inset-inline-end: var(--space-md);
  }

  /* ---------------------------------------------------------------------------
  CLOSE BUTTON — top-right
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="close"] {
    pointer-events: auto;
    position: absolute;
    inset-block-start: var(--space-sm);
    inset-inline-end: var(--space-sm);
    z-index: 10;
  }

  /* ---------------------------------------------------------------------------
  COUNTER — slide count indicator (optional)
  --------------------------------------------------------------------------- */

  :where(ui-lightbox, [data-ui~="lightbox"]) [data-lightbox-slot~="counter"] {
    position: absolute;
    inset-block-end: var(--space-sm);
    /* left + physical translate is a direction-neutral centering idiom; keep physical */
    left: 50%;
    translate: -50% 0;
    opacity: 0.7;
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
  }
}
// primitives/carousel/carousel.js
"use strict";
import { EmblaInit } from "../../base/embla.js";
import { ZazzElement, defineZazzElement } from "../../base/zazz-element.js";
class UiCarouselElement extends ZazzElement {
    setup(signal) {
        const dialog = this.closest("dialog");
        if (dialog && !dialog.open) {
            // Closed dialogs are display:none — Embla can't measure the viewport.
            // Initialize on the dialog's open instead (zazz:dialog-open).
            dialog.addEventListener("zazz:dialog-open", () => this.init(), { signal });
            return;
        }
        this.init();
    }
    teardown() {
        // Abort first so the per-carousel DOM listeners (prev/next, dots, thumbs,
        // drag-click suppression) are removed before the Embla instances are torn down.
        this._emblaController?.abort();
        delete this._emblaController;
        this._emblaApi?.destroy();
        this._emblaApiThumb?.destroy();
        delete this._emblaApi;
        delete this._emblaApiThumb;
        // Allow re-initialization if the element is re-inserted.
        this.removeAttribute("data-carousel-init");
    }
    /**
     * @description Initializes the carousel. Idempotent — already-initialized
     * roots and roots inside closed dialogs are skipped by `initRoot`.
     */
    init() {
        EmblaInit.initRoot(this);
    }
    /**
     * @returns The Embla API, or null before initialization.
     */
    get api() {
        return this._emblaApi ?? null;
    }
}
defineZazzElement("ui-carousel", UiCarouselElement);
export { UiCarouselElement };

// primitives/lightbox/lightbox.js
"use strict";
/**
 * @fileoverview `<ui-lightbox>` — HTML web component for lightbox galleries.
 * @description Light-DOM custom element that coordinates the two carousels in
 * a lightbox: the inline gallery and the fullscreen `<dialog>` slideshow.
 *
 * Responsibilities (subscribing to `zazz:dialog-open` / `zazz:dialog-close`
 * from base/dialog-lifecycle.ts):
 * - On dialog open: initializes the dialog's `<ui-carousel>` (deferred
 *   while the dialog was closed), jumps it to the gallery's current slide,
 *   and focuses the viewport so keyboard navigation works immediately.
 * - On dialog close: scrolls the inline gallery to the last viewed slide.
 *
 * Opening and closing the dialog itself needs no JavaScript — slides carry
 * `command="show-modal"` / `command="close"` (Invoker Commands). Drag-aware
 * click suppression on the stage and thumbs is wired by `initRoot` in
 * embla.js (keyed generically on command-bearing `carousel-slide` slots).
 *
 * @example
 * <ui-lightbox>
 *   <div data-lightbox-slot="gallery">
 *     <ui-carousel data-lightbox-slot="stage" data-carousel-loop="true">…</ui-carousel>
 *   </div>
 *   <dialog data-ui="dialog" data-lightbox-slot="dialog" closedby="any">
 *     <ui-carousel data-carousel-loop="true">…</ui-carousel>
 *   </dialog>
 * </ui-lightbox>
 */
import { UiCarouselElement } from "../carousel/carousel.js";
import { ZazzElement, defineZazzElement } from "../../base/zazz-element.js";
class UiLightbox extends ZazzElement {
    setup(signal) {
        const dialog = this.querySelector("dialog");
        if (!(dialog instanceof HTMLDialogElement))
            return;
        // The dialog is a descendant, so its lifecycle events bubble through
        // this element: subscribe here instead of observing attributes.
        this.addEventListener("zazz:dialog-open", (e) => {
            if (e.target === dialog)
                this.#onDialogOpen(dialog);
        }, { signal });
        this.addEventListener("zazz:dialog-close", (e) => {
            if (e.target === dialog)
                this.#syncGalleryToDialog(dialog);
        }, { signal });
    }
    /**
     * @returns The inline gallery's carousel root.
     */
    #galleryRoot() {
        return this.querySelector('[data-lightbox-slot~="gallery"] :is(ui-carousel, [data-ui~="carousel"])');
    }
    /**
     * @description Initializes the dialog carousel, opens it at the gallery's
     * current slide, and moves focus to the slideshow viewport.
     *
     * @param dialog - The lightbox dialog.
     */
    #onDialogOpen(dialog) {
        const dialogRoot = dialog.querySelector(':is(ui-carousel, [data-ui~="carousel"])');
        if (!dialogRoot)
            return;
        // <ui-carousel> defers init while its dialog is closed. Its own
        // zazz:dialog-open listener (on the dialog) fires before this one (on the
        // ancestor) by DOM dispatch order, but init here too — idempotent — so the
        // jump below never races a class-form root.
        if (dialogRoot instanceof UiCarouselElement) {
            dialogRoot.init();
        }
        const galleryApi = this.#galleryRoot()?._emblaApi;
        if (dialogRoot._emblaApi && galleryApi) {
            dialogRoot._emblaApi.scrollTo(galleryApi.selectedScrollSnap(), true);
        }
        const viewport = dialogRoot.querySelector('[data-carousel-slot~="viewport"]');
        if (viewport instanceof HTMLElement) {
            viewport.focus({ preventScroll: true });
        }
    }
    /**
     * @description Scrolls the inline gallery to the slide last viewed in the dialog.
     *
     * @param dialog - The lightbox dialog.
     */
    #syncGalleryToDialog(dialog) {
        const dialogApi = dialog.querySelector(':is(ui-carousel, [data-ui~="carousel"])')?._emblaApi;
        const galleryApi = this.#galleryRoot()?._emblaApi;
        if (dialogApi && galleryApi) {
            galleryApi.scrollTo(dialogApi.selectedScrollSnap());
        }
    }
}
defineZazzElement("ui-lightbox", UiLightbox);
export { UiLightbox };

Behavior

primitives/lightbox/lightbox.js

<ui-lightbox> — HTML web component for lightbox galleries. Light-DOM custom element that coordinates the two carousels in a lightbox: the inline gallery and the fullscreen <dialog> slideshow.

Responsibilities (subscribing to zazz:dialog-open / zazz:dialog-close from base/dialog-lifecycle.ts):

  • On dialog open: initializes the dialog's <ui-carousel> (deferred while the dialog was closed), jumps it to the gallery's current slide, and focuses the viewport so keyboard navigation works immediately.
  • On dialog close: scrolls the inline gallery to the last viewed slide.

Opening and closing the dialog itself needs no JavaScript — slides carry command="show-modal" / command="close" (Invoker Commands). Drag-aware click suppression on the stage and thumbs is wired by initRoot in embla.js (keyed generically on command-bearing carousel-slide slots).

Events: zazz:dialog-close, zazz:dialog-open

Attributes

lightbox attributes
AttributeKindValuesNotes
data-ui="lightbox"Identityfree valueIdentity token in data-ui.
<ui-lightbox>Tag formfree valueTag form; styled and, when scripted, registered under this name.
data-lightbox-slotSlotclose content counter dialog gallery next prev slide stage thumb thumb-content thumbs thumbs-next thumbs-prevSlot: a part inside the primitive.

Requires: dialog, carousel, button.

Hooks

Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.

lightbox hooks
HookDefaultState
--ui-lightbox-aspect3 / 2—
--ui-lightbox-img-border1px solid var(--color-faded)—
--ui-lightbox-img-filter--activebrightness(0.5)active
--ui-lightbox-img-filter--hoverbrightness(0.8)hover
--ui-lightbox-img-opacity1—
--ui-lightbox-img-scale1—
--ui-lightbox-img-scale--active1active
--ui-lightbox-img-scale--hover1.03hover
--ui-lightbox-img-transition-delay0ms—
--ui-lightbox-img-transition-durationcalc(var(--default-transition-duration) * 3)—
--ui-lightbox-img-transition-propertytransform, filter—
--ui-lightbox-img-transition-timing-functionvar(--default-transition-timing-function)—
--ui-lightbox-object-fitcover—
--ui-lightbox-slide-opacity--end1end
--ui-lightbox-slide-opacity--start0start
--ui-lightbox-slide-roundedvar(--radius-lg)—
--ui-lightbox-slide-scale--end1end
--ui-lightbox-slide-scale--start0.9start
--ui-lightbox-thumb-columns6—
--ui-lightbox-thumb-gapcalc(var(--spacing) * 1.5)—
--ui-lightbox-thumb-opacity0.5—
--ui-lightbox-thumb-opacity--active1active
--ui-lightbox-thumb-roundedvar(--radius-md)—
--ui-lightbox-thumb-sizecalc(var(--spacing) * 16)—