API reference
Each part accepts native element props for its default host (via Base UI render where noted). Custom props are listed below. Guides: Composition, View transitions, Gestures.
By concern
- State
- Trigger / Item
- Layout
- View transitions
- Shell
- Gestures
- Chrome
Root
Groups all parts of the lightbox and owns open state, active index, view-transition morph, scroll locking, and the active-zoom bridge. Pass handle from Lightbox.createHandle() for imperative control and detached Triggers. Prefer onOpenChangeComplete for post-motion work; use scrollTriggerIntoView so close morphs land on-screen when the page scrolled while open.
Props
Trigger
Opens the lightbox at a given index and is the morph source (view transitions on by default). Carries the Base UI dialog-trigger disclosure semantics: aria-haspopup="dialog", aria-controls pointing at the dialog while open, and aria-expanded set only on the trigger that opened it. Pass handle + id to use outside Root or for handle.open(..., { triggerId }). Prefer render-prop children to attach imageRef / morphRef.
Props
Data attributes
data-ramka-triggerAlways present on the trigger element.
data-popup-openPresent when the corresponding lightbox is open.
data-activePresent when this trigger’s index is the active item.
data-morph-targetPresent on the resolved morph element while it is the open/close morph source: the trigger root by default, or the node designated via morphRef when attached. Style hide/opacity against this attribute directly — do not assume it lives on the Trigger button.
data-crossfadeReflects the crossfade prop: open | close | both.
Portal
Portals Backdrop and Content into a host element (default: document.body). Pass container / keepMounted here. keepMounted parks Backdrop+Content while closed (inert, hidden). When using keepMounted, style z-index on the Portal host yourself (Base UI). Prefer isolation: isolate on the app layout root.
Props
Data attributes
data-ramka-portalAlways present on the portal host element.
Content
Dialog surface (role="dialog", aria-modal). Provide an accessible name via aria-label or aria-labelledby (required by the WAI-ARIA dialog pattern; the library warns in development if it is missing). Place inside Lightbox.Portal. Owns focus trap, Escape, and pull-to-dismiss. When Lightbox.Stage is mounted, gallery nav (arrows/Home/End), zoom shortcuts (⌘/+/-), pinch/ctrl+wheel zoom, and touch/wheel pull are scoped to Stage (and Zoom pan uses Stage as viewport); otherwise Content itself is the stage. Keyboard pan vs navigate depends on whether the active Zoom is above 1×.
Props
Data attributes
data-ramka-contentAlways present on the dialog surface.
data-openPresent while the lightbox is open.
data-closedPresent while the lightbox is closed (still rendered).
data-pullingPresent while a pull-to-dismiss drag is in progress.
data-pull-snappingPresent while a cancelled pull springs back to rest. data-pulling is dropped on release so chrome can transition; this latch keeps pull-only layout (overflow, shell hide) until the item is parked.
data-pull-dismissingPresent while the dismiss animation runs after crossing the threshold.
data-zoomingPresent during an active pinch/zoom gesture (blocks pull-to-dismiss).
data-zoomedPresent when content zoom is greater than 1× (held through zoom-out animation).
CSS variables
--lightbox-pull-progress0 at rest → 1 at the dismiss threshold. Drive backdrop/content opacity from this.
--lightbox-pull-snap-durationDuration of the pull-to-dismiss snap-back, published only while the cancelled pull springs back to rest. Reference it in your transition so progress-derived visuals stay in lockstep with the item's spring, e.g. .transition: opacity var(--lightbox-pull-snap-duration, 300ms) var(--lightbox-pull-snap-easing, ease)
--lightbox-pull-snap-easingEasing of the pull-to-dismiss snap-back (a velocity-aware spring, published only during the spring-back). Pair with --lightbox-pull-snap-duration so backdrop/chrome fades match the item's snap-back exactly.
--lightbox-zoom-progress0 at 1× → 1 once zoom has risen by 1× above min (e.g. 2× when min is 1). Written by the active from the *visual* scale (live WAAPI transform while a discrete zoom animates; React zoom otherwise). Cleared when zoom returns to rest. Drive sibling dim/blur from this, e.g. Lightbox.Zoom.filter: opacity(calc(1 - var(--lightbox-zoom-progress, 0))) blur(calc(var(--lightbox-zoom-progress, 0) * 6px))
Stage
Optional media interaction surface inside Content. Scopes touch and trackpad wheel pull-to-dismiss, Zoom pan viewport geometry, arrow/Home/End gallery navigation, zoom shortcuts (⌘/+/-), and pinch/ctrl+wheel zoom to the media pane. Interactive controls inside Stage keep their press (Close, Prev/Next, etc.). Place side panels / threads as siblings of Stage under Content. When omitted, Content itself is the stage. Prefer render-prop children to attach morphRef when the open/close morph should capture this surface (or a descendant) instead of the active item.
Props
Data attributes
data-ramka-stageAlways present on the media interaction stage.
Backdrop
Optional scrim (place inside Lightbox.Portal, typically beside Content). Click closes the lightbox. Tracks pull-to-dismiss progress.
Props
Data attributes
data-ramka-backdropAlways present on the backdrop element.
data-openPresent while the lightbox is open.
data-closedPresent while the lightbox is closed (still rendered).
data-pullingPresent while pull-to-dismiss is active (mirrors Content).
data-pull-snappingPresent while a cancelled pull springs back (mirrors Content).
CSS variables
--lightbox-pull-progressSame progress variable as Content during pull-to-dismiss.
--lightbox-pull-snap-durationSame snap-back duration variable as Content during pull-to-dismiss.
--lightbox-pull-snap-easingSame snap-back easing variable as Content during pull-to-dismiss.
Close
Button that closes the lightbox. Defaults to aria-label="Close".
Props
Data attributes
data-ramka-closeAlways present on the close button.
Item
A single lightbox item. Registers captions and morph/placeholder refs. ARIA adapts automatically: it is a tabpanel wired (aria-labelledby / aria-controls) to its Lightbox.Thumbnail tab when a Lightbox.ThumbnailGroup is present, otherwise a carousel slide (role="group", aria-roledescription="slide", labelled "N of total"). Prefer nesting Lightbox.Media (sized morph + placeholder host) around the destination <img>/<video>; imageRef / morphRef remain escape hatches. Typically nested inside Slide. Outside Slides, every item stays mounted; inside Slides, `preload` gates contents.
Props
Data attributes
data-ramka-itemAlways present on the item element.
data-activePresent when this item is the active index.
data-indexThe item index as a string.
data-skip-fadeTransient during view-transition open — suppress consumer entrance fades on the destination item.
Media
Sized destination media host — morph target and placeholder canvas container. Structural props: width/height (fit-box, same idea as Next.js Image); display:grid plus a scoped <style> so direct img/video fill the box (HTML dimension attrs cannot pin intrinsic size). Cosmetics (radius, colors, object-fit) stay consumer-owned. Prefer Zoom > Media > img|video (Zoom is a size container for Media’s cqw/cqh fit-box; put resting chrome insets on Slide padding). Auto-discovers the primary <img> (or video poster) for decode / thumbnail bridging.
Props
Data attributes
data-ramka-mediaAlways present on the media host.
data-placeholderPresent while the open-morph placeholder canvas is bridging.
data-widthPresent when a structural fit-box width is resolved (prop or img dims).
data-heightPresent when a structural fit-box height is resolved (prop or img dims).
Slides
Default layout: horizontal snap carousel wired to the active index. Exposes the WAI-ARIA carousel container (role="group", aria-roledescription="carousel") — give it a label via aria-label / aria-labelledby. Gates Item contents via `preload`; put Slide → Item → Zoom → Media inside. Consumer CSS sizes each Slide snap box (e.g. flex 0 0 100% for full-bleed); index geometry measures offsetLeft/offsetWidth. While the user is flinging, navigation is locked so pull-to-dismiss and zoom chrome do not fight the scroll.
Props
Data attributes
data-ramka-slidesAlways present on the slides scrollport.
data-active-indexCurrent active index.
data-directionforward or backward.
data-orientationAlways horizontal.
Slide
One snap-aligned slide box inside Slides. Snap alignment (`scroll-snap-align`, `scroll-snap-stop`) is built in; slide width (full-bleed, aspect-sized cards, …) is consumer CSS — set `flex` / `width` / `min-width` via className or style. Place a Lightbox.Item (and typically Zoom → Media) inside. Prefer putting resting chrome / safe-area insets as padding on Slide (not Zoom) so Item/Zoom fill the content box and zoomed media can paint into the pad under chrome.
Data attributes
data-ramka-slideAlways present on the slide element.
Previous
Navigate to the previous item. Disabled at the start when loop is false.
Props
Data attributes
data-ramka-previousAlways present on the previous button.
data-disabledPresent when navigation is unavailable.
Next
Navigate to the next item. Disabled at the end when loop is false.
Props
Data attributes
data-ramka-nextAlways present on the next button.
data-disabledPresent when navigation is unavailable.
Counter
Shows the active position (e.g. "1 / 12") with an aria-live region.
Props
Data attributes
data-ramka-counterAlways present on the counter element.
Caption
Renders the active item’s caption from the Item caption registry.
Props
Data attributes
data-ramka-captionAlways present on the caption element.
data-has-captionPresent when the active item has a caption.
ThumbnailGroup
Accessible tablist of thumbnails with arrow / Home / End keyboard support.
Props
Data attributes
data-ramka-thumbnail-groupAlways present on the thumbnail group (tablist).
data-focus-withinPresent when focus is inside the group.
Thumbnail
A single thumbnail tab that activates the given index.
Props
Data attributes
data-ramka-thumbnailAlways present on the thumbnail button.
data-activePresent when this thumbnail’s index is active.
data-indexThe thumbnail index as a string.
ThumbnailStrip
Scroll-linked thumbnail strip root. Always centers the active thumbnail on the midline (Photos-style): owns overflow clip, measured midline track padding (from registered Thumbnail nodes — not role=tab queries), and Slides→track sync via native ScrollTimeline + WAAPI (JS scroll→transform fallback when ScrollTimeline is unavailable). Compose with ThumbnailStripTrack + Thumbnails; optional midline selection chrome is plain consumer markup (strip root is position:relative). Discrete nav (pointer thumb, keyboard, Previous/Next) goes through ThumbnailStrip (DocumentTimeline WAAPI track scrub via scrubOnSelect; element-scoped Slides view transition on pointer select via selectViewTransition — tune with --lightbox-select-vt-duration / --lightbox-select-vt-easing on Slides; optional wrapSelectCommit(commit, slides, index) to arm consumer WAAPI such as an indicator scrub before the scroll jump). Keyboard / Previous / Next keep an instant stage commit while the strip still scrubs. Flex/gap/thumb size remain consumer CSS.
Props
Data attributes
data-ramka-thumbnail-stripAlways present on the thumbnail strip root.
data-ramka-thumbnail-strip-trackAlways present on the scroll-linked tablist track.
data-scrubbingPresent while a discrete-nav scrub animation is in flight.
CSS variables
--lightbox-thumbnail-strip-travelEnd translateX travel for the scroll link (negative px length to center the last thumb — when thumbs are uniform). Set on the ThumbnailStripTrack.(count - 1) * step
--lightbox-select-vt-durationDuration of the element-scoped Slides view transition on pointer thumb select (selectViewTransition). Set on Slides (or a parent) and use from — library starts the transition; CSS owns the look. Default fallback in demos: ::view-transition-group(root)0.4s.
--lightbox-select-vt-easingEasing of the element-scoped Slides view transition on pointer thumb select. Pair with --lightbox-select-vt-duration. Default fallback in demos: .cubic-bezier(0.22, 1, 0.36, 1)
ThumbnailStripTrack
Linked tablist track for ThumbnailStrip. A ThumbnailGroup that registers as the ScrollTimeline transform target (same keyboard / ARIA). Parent ThumbnailStrip writes midline padding + travel CSS var and owns the link animation; flex, gap, and thumb sizing are consumer CSS.
Props
Zoom
Pinch, wheel, double-tap, and keyboard zoom/pan surface. Fills its host and is a CSS size container (`container-type: size`) for nested Media fit-box units. Does not set overflow (clipping belongs on Content / chrome so zoomed media can escape the resting box). Prefer Zoom > Media > img|video so pan/maxZoom use Media’s content box. Publishes to Root’s active-zoom bridge so ZoomIn/ZoomOut in toolbar chrome can target the active slide.
Props
Data attributes
data-ramka-zoomAlways present on the zoom container.
data-zoomPresent when zoom > 1.
data-draggingPresent while panning with a pointer.
ZoomIn
Increments zoom. Works inside Zoom, or as global chrome via the active-slide zoom bridge.
Props
Data attributes
data-ramka-zoom-inAlways present on the zoom-in button.
data-disabledPresent when zoom-in is unavailable.
ZoomOut
Decrements zoom. Works inside Zoom, or as global chrome via the active-slide zoom bridge.
Props
Data attributes
data-ramka-zoom-outAlways present on the zoom-out button.
data-disabledPresent when zoom-out is unavailable.
Document (view transitions)
While a view-transition morph runs, ramka sets attributes and CSS variables on <html> so you can style ::view-transition-* pseudo-elements. Cleared when the transition finishes.
Data attributes
data-ramka-view-transitionPresent during a morph. Value is opening or closing.
data-ramka-crossfadePresent while the morph dissolves trigger↔item snapshots (Trigger crossfade is active for this phase). Absent for the default hard-cut.
data-ramka-vt-presetPresent during a morph. Value is the active viewTransition timing preset name (default, snappy, relaxed, spring) — read by the composited morph and available to skin CSS for per-preset styling.
CSS variables
--lightbox-morph-border-radius-fromStart border-radius for the morph group clip (interpolatable px).
--lightbox-morph-border-radius-toEnd border-radius for the morph group clip.
--lightbox-morph-old-opacityOpacity of the old morph snapshot on the hard-cut path (no document crossfade).
--lightbox-morph-new-opacityOpacity of the new morph snapshot on the hard-cut path (1 on open, 0 on close).
--lightbox-vt-durationMorph + root-snapshot duration from the Root's viewTransition timing preset. WRITTEN BY THE LIBRARY in two places — do not set it yourself: onto documentElement during a morph (read it in document-level rules, e.g. ::view-transition-* for a derived track), and always onto the Portal host, so skin open/close fades inside the portal can use the preset's clock even when no morph runs (non-VT browsers, reduced motion, low-end fallback), e.g. calc(var(--lightbox-vt-duration) * 0.5).transition: opacity var(--lightbox-vt-duration, 360ms) ease-out
--lightbox-vt-easingEasing for and the *open* crossfade dissolve. Written by the library from the ::view-transition-group(lightbox-morph)viewTransition preset, like --lightbox-vt-duration (during a morph on documentElement, always on the Portal host). Close dissolve uses --lightbox-vt-crossfade-close-easing (a shorter complementary fade, not the morph curve) so the item yields before the size settle.
--lightbox-vt-crossfade-close-easingClose trigger↔item snapshot dissolve, used while and data-ramka-view-transition="closing"data-ramka-crossfade are set. Modest ease-out on a shorter window that still runs old 1→0 and new 0→1 on the same clock (plus-lighter stays opaque). Written by the library onto documentElement during a morph — do not set it yourself.
--lightbox-vt-root-easingEasing for the root snapshot cross-fade (). Written by the library from the ::view-transition-old/new(root)viewTransition preset, like --lightbox-vt-duration (during a morph on documentElement, always on the Portal host). A good default for skin backdrop/content fades.
Types
Helper types you will see in callbacks and render props (exported from @ramka/react / the Lightbox module):
NavigationSource—'trigger' | 'keyboard' | 'thumbnail' | 'button' | 'scroll' | 'imperative'. Second argument to RootonValueChange. Handleopen/goTouse'imperative'; scroll-driven Slides use'scroll'.LightboxCrossfade—'open' | 'close' | 'both'for Triggercrossfade. See View transitions → crossfade.- Render-state types such as
LightboxTriggerRenderStateand Item render args (active,visible, refs) — documented on Trigger / Item.
Hooks
Optional contexts for nested wrapper components.
useLightboxTriggerContext
Optional context from the enclosing Trigger. Use in nested wrappers (e.g. a shared <TriggerImage>) to attach imageRef / morphRef without threading the render prop.
Props
useLightboxItemContext
Optional context from the enclosing Item. Use in nested wrappers to attach imageRef / morphRef or read active/index. Prefer Lightbox.Media for the common morph/placeholder host.
Props
useLightboxMediaContext
Optional context from the enclosing Media. Exposes the Media host element for advanced wrappers.
Props
useLightboxStageContext
Optional context from the enclosing Stage. Use in nested wrappers to attach morphRef without threading the render prop — the shared View Transition capture destination.
Props
Imperative handles
Use Lightbox.createHandle() on Root (and optional detached Triggers). Zoom exposes zoom controls via ref.
LightboxHandle
Imperative handle from Lightbox.createHandle(). Pass to Root (and optional detached Triggers).
Props
LightboxZoomRef
Imperative handle from ref on Zoom.