Lightbox
A full-viewport image viewer for a gallery. Not a trigger itself: render your own thumbnails and drive index (controlled or uncontrolled) from their click handlers.
Real use · thumbnail grid opens a full-viewport viewer
Using Lightbox
Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.
Plain HTML — CSS classes
The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Lightbox is styled but inert: its behavior comes from modal. For that behavior with no framework, use the node-tree path below.
/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";Root class lightbox-backdrop, with 8 modifiers:
.lightbox-backdrop.lightbox-caption.lightbox-close.lightbox-counter.lightbox-image.lightbox-nav.lightbox-nav-next.lightbox-nav-prev.lightbox-popup
React
Typed components over those same classes, with behavior and accessibility from Base UI.
npm i @wizeworks/silicaui-reactimport { Lightbox } from "@wizeworks/silicaui-react";Props
| Prop | Type | Required | Description |
|---|---|---|---|
| items | LightboxItem[] | yes | — |
| index | number | null | no | Controlled open index; null (or undefined while uncontrolled) is closed. |
| defaultIndex | number | null | no | — |
| onIndexChange | (index: number | null) => void | no | — |
| loop | boolean | no | Wrap past the first/last item with the nav arrows. Default true. |
| className | string | no | — |
Source of the demo above
The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.
Show source
import { useState } from "react";
import { Lightbox, type LightboxItem } from "@wizeworks/silicaui-react";
import { Section } from "../lib/Section";
// Stand-in "photos" (data URIs, always load offline): a gradient tile with a
// number, so the viewer/nav path is demonstrated without a network request.
function placeholder(n: number, from: string, to: string): string {
return `data:image/svg+xml,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='800' height='600'>` +
`<defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>` +
`<stop offset='0' stop-color='${from}'/><stop offset='1' stop-color='${to}'/>` +
`</linearGradient></defs>` +
`<rect width='800' height='600' fill='url(#g)'/>` +
`<text x='400' y='330' font-size='180' font-family='sans-serif' font-weight='700' fill='rgba(255,255,255,.85)' text-anchor='middle'>${n}</text>` +
`</svg>`,
)}`;
}
const PHOTOS: LightboxItem[] = [
{ src: placeholder(1, "#6366f1", "#ec4899"), caption: "Photo 1" },
{ src: placeholder(2, "#f97316", "#eab308"), caption: "Photo 2" },
{ src: placeholder(3, "#10b981", "#06b6d4"), caption: "Photo 3" },
{ src: placeholder(4, "#8b5cf6", "#ec4899"), caption: "Photo 4" },
];
export function LightboxDemo() {
const [index, setIndex] = useState<number | null>(null);
return (
<Section title="Real use · thumbnail grid opens a full-viewport viewer">
<div className="grid grid-cols-4 gap-2">
{PHOTOS.map((p, i) => (
<button
key={p.src}
type="button"
className="overflow-hidden rounded-field"
onClick={() => setIndex(i)}
>
<img src={p.src} alt="" className="block h-24 w-full object-cover" />
</button>
))}
</div>
<Lightbox items={PHOTOS} index={index} onIndexChange={setIndex} />
</Section>
);
}Vanilla — node tree to HTML
Lightbox — trigger[i] opens slide[i] (positional pairing, like tabs); Counter reuses the title role (same "text this behavior keeps in sync" convention calendar's month label uses). Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.
npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviorsimport { atom, toHtml } from "@wizeworks/silicaui-html";
const node = atom("Lightbox", "lightbox-backdrop", { /* props */ }, [/* children */]);
const markup = toHtml(node);Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "lightbox-backdrop lightbox-backdrop-primary" rather than passing a colour as a prop.
- Takes children
- yes
- Hydrated by
modal
Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.
The full API for Lightbox, on every path, is available through the Silica MCP server.