Skip to content

Instantly share code, notes, and snippets.

@The-LukeZ
Last active July 20, 2026 15:00
Show Gist options
  • Select an option

  • Save The-LukeZ/c315c091578564b6bfc00ea577778d4a to your computer and use it in GitHub Desktop.

Select an option

Save The-LukeZ/c315c091578564b6bfc00ea577778d4a to your computer and use it in GitHub Desktop.
Astro Starlight Image Lightbox Integration
// Import it somewhere top-level in like the MarkdownContent.astro override
<script src="../../scripts/imageLightbox.js"></script>
/* Image lightbox open/close transitions. */
#image-lightbox {
opacity: 0;
scale: 0.95;
}
#image-lightbox[open] {
opacity: 1;
scale: 1;
}
#image-lightbox::backdrop {
background: rgba(0, 0, 0, 0);
}
#image-lightbox[open]::backdrop {
background: rgba(0, 0, 0, 0.85);
}
@media (prefers-reduced-motion: no-preference) {
#image-lightbox {
transition:
opacity 0.25s ease,
scale 0.25s ease,
overlay 0.25s ease allow-discrete,
display 0.25s ease allow-discrete;
}
#image-lightbox::backdrop {
transition:
background 0.25s ease,
overlay 0.25s ease allow-discrete,
display 0.25s ease allow-discrete;
}
@starting-style {
#image-lightbox[open] {
opacity: 0;
scale: 0.95;
}
#image-lightbox[open]::backdrop {
background: rgba(0, 0, 0, 0);
}
}
}
// This is a POC
// Click any <img> inside an ImageWrapper to open it full-size in a <dialog>.
// Dialog element is created on demand and appended to <body>, outside the
// normal component tree, since it needs to escape ImageWrapper's overflow-clip.
let dialog;
function getDialog() {
if (dialog) return dialog;
dialog = document.createElement("dialog");
dialog.id = "image-lightbox";
const figure = document.createElement("figure");
figure.id = "image-lightbox-figure";
const img = document.createElement("img");
img.id = "image-lightbox-img";
figure.appendChild(img);
const caption = document.createElement("figcaption");
caption.id = "image-lightbox-caption";
figure.appendChild(caption);
dialog.appendChild(figure);
Object.assign(dialog.style, {
position: "fixed",
inset: "0",
margin: "auto",
padding: "0",
border: "none",
background: "transparent",
width: "fit-content",
height: "fit-content",
maxWidth: "100vw",
maxHeight: "100vh",
});
Object.assign(figure.style, {
margin: "0",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "0.5rem",
maxWidth: "100vw",
maxHeight: "100vh",
});
Object.assign(img.style, {
display: "block",
width: "auto",
height: "auto",
maxWidth: "100vw",
maxHeight: "calc(100vh - 3rem)",
objectFit: "contain",
});
Object.assign(caption.style, {
color: "white",
textAlign: "center",
fontSize: "0.875rem",
});
// Click anywhere except the caption text closes the dialog.
dialog.addEventListener("click", (event) => {
if (event.target === caption) return;
dialog.close();
});
document.body.appendChild(dialog);
return dialog;
}
document.addEventListener("click", (event) => {
const target = event.target;
if (
!(target instanceof HTMLElement) ||
target.tagName.toLowerCase() !== "img"
)
return;
const wrapper = target.closest('[data-type="image-wrapper"]');
if (!wrapper) return;
const dlg = getDialog();
const dlgImg = dlg.querySelector("img");
const dlgCaption = dlg.querySelector("figcaption");
dlgImg.src = target.currentSrc || target.src;
dlgImg.alt = target.alt;
dlgCaption.textContent = target.alt;
dlgCaption.style.display = target.alt ? "block" : "none";
dlg.showModal();
});
---
// I have a component to wrap images for better styling as well - do what you want.
interface Props {
size?: "sm" | "md" | "lg" | "xl" | "full";
class?: string;
}
const { size = "md", class: customClasses = "" } = Astro.props; // Default width for optimization
const sizeClasses = (_size: Props["size"] = "md") =>
({
sm: "max-w-sm",
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
full: "max-w-full",
})[_size];
---
<div
class={`mx-auto overflow-clip ${sizeClasses(size)} ${customClasses}`.trim()}
data-type="image-wrapper"
>
<slot />
</div>
<style>
[data-type="image-wrapper"] :global(img) {
cursor: zoom-in;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment