docs/components/lightbox
Lightboxpreview
A full-screen viewer that opens from its triggers, with mobile gestures and zoom.
Usage guidelines#
- The images fly: the image grows out of its trigger when the viewer opens and flies back when it closes, to its own trigger or to the one
closeTargetpicks, such as a "+5" tile. Hide the trigger while it hasdata-popup-open. - One gesture engine: swipe, pinch, double tap, pan and pull to dismiss are handled together, so they never fight over a touch. A long drag can cross multiple slides and settles at the position reached, rather than advancing only one. The viewer needs no carousel or zoom library.
- Your layout, your items: the lightbox renders no thumbnails of its own. Lay out your page however you like and place a
Lightbox.Triggerper item. Items can have any shape; the lightbox only needs a stable value for each. - No dependencies: the lightbox needs no carousel, zoom, animation or image library, and no framework.
Lightbox.Mediawraps your own<img>,<video>or<iframe>; give it anysrcorsrcSet.
Anatomy#
Import the component and assemble its parts:
Examples#
Getting started#
The smallest viewer: triggers, the images and a close button. Swipe, pinch, double tap, pull to dismiss and the keyboard all work.
Lightbox.Root takes the items in items, with any shape. Each item needs a value that identifies it and stays the same when items are added, removed or reordered: by default its id, or whatever itemToValue returns. Lightbox.Trigger opens the viewer on the item with its value. Lightbox.Viewport calls its function child for the item shown and its neighbours; the parts take what they need as props. Your <img> gets its src and alt, and Lightbox.Media gets the natural width and height, which give it the right shape before the image loads.
A single image#
One trigger opens one image, here a profile photo with the zoom only. The image grows out of the circle: the flight takes each corner radius of its trigger. Round the triggers themselves (first:rounded-l-xl), not a container that clips them (overflow-hidden rounded-xl): the flight can't see a parent's clip. The gallery at the top of the page does the same with its corner tiles.
Maya Lindqvist
Photographer
Chat attachments#
One lightbox for the whole conversation: swipe from one message's images to the next. The last tile of a message counts the images it hides, which the viewer still shows; Dots pick the image, with a bright dot gliding over the active one.
The messages scroll. As you move through the images, the list scrolls behind the viewer to the message of the image shown (scrollToTrigger, on by default), so closing always flies back to a tile you can see. With closeTarget="nearest", an image hidden behind "+1" flies back to the "+1" tile, which stays empty while such an image is shown, and turns into it on the way.
Lightbox.createHandle() links a lightbox to code and to triggers outside Lightbox.Root, like Base UI's detached triggers: pass the same handle to the root, and to each Lightbox.Trigger placed elsewhere. Here "Shared media" opens the first image with sharedMedia.open(value). Its tile is scrolled out of view, so the image fades in rather than flying from a place you can't see.
# weekend-trip
3 members
Where the image lands#
When the viewer closes, the image flies back to a trigger on the page, and focus returns to it. closeTarget on Lightbox.Root picks which:
"active"(the default): the trigger of the item shown. Without one on the page, the image fades out where it is."opener": the trigger that opened the viewer, whichever item is shown. For a cover that opens a whole album, like the ones below."nearest": the trigger of the item shown, or else the trigger of the nearest item that has one. For a grid that shows a few items and a "+5" tile, like the gallery at the top of the page.
A trigger that shows something other than its media takes crossfade: an avatar for a story, or a tile with a "+5" count or a play badge, which then fade out as the image leaves and back in as it lands. With it, the image grows out of its look as the viewer opens, and turns back into it on the way home, like the stories below.
The trigger the image will fly back to has data-popup-open the whole time the viewer is open, following the item shown: hide it (data-popup-open:invisible) and its slot waits empty, for the drop after a drag too. When that trigger shows another image (the cover, the "+5" tile), a copy of it flies along with the image and fades in over it, so the image turns into the trigger on the way, and lands looking exactly like it. A trigger that can't be seen, scrolled out of the window or out of a list that clips it, gets the image faded out instead; scrollToTrigger scrolls the page to it beforehand, as in the chat above.
Iceland
4 photos
Faces and places
4 photos
Instagram-style stories#
The people stay in one horizontally scrollable row on every screen size. Their rings change color and opacity on hover without lifting the avatars. The demo uses select-none to avoid accidental selection, except for the reply field, which keeps select-text.
Each ring is a person, and the items of the lightbox are the people: swipe, or drag past the edge of the card, to go from one person to the next. A person's own stories play inside their slide, like Instagram:
- They play by themselves. One bar per story fills for five seconds, or for the length of a video, then the next story shows, then the next person. After the last one, the viewer closes, and the ring turns grey.
- Tap the left third of the card to go back, anywhere else to go on; ← → do the same. Hold to pause; the controls fade while you hold. The pause button and typing a reply pause too, as WCAG asks of content that moves on by itself.
- Controlled. The stories move on to the next person by changing
value, and the viewer slides there as it does for Next; after the last person,openturnsfalse.onOpenChangeCompletestarts the bars once the card has landed. The viewer loads the story the person is on before the card flies out of their ring. - Keys of your own. The popup's
onKeyDownruns before its shortcuts: it takes ← → for the stories and prevents the default, so the library doesn't also change the person.
The rings take crossfade, since they show a face rather than the story: the card grows out of the face as it opens, and turns back into it as it flies home.
Every control sits on the story card, inside Lightbox.Media: the bars, the author, pause, Lightbox.Close, the story's text in a Lightbox.Caption, and the reply field. So they slide with the person during a swipe, follow the pull to dismiss, and fly back into the ring with the card. Controls inside a slide get their clicks: a press that starts on a button, a link or a field isn't a gesture. maxScale={1} keeps the stories at their resting scale.
Every story is the same shape: Lightbox.Media takes fill, so the card fills its slide whatever the shape of the photo or video, and object-cover on the media covers it. The viewport sets the size in CSS: 9:16 on a large screen, the whole screen on a phone.
The stories turn like the faces of a cube, in CSS only. Each Lightbox.Slide has --lightbox-slide-progress, its distance from the centre in slides (0 shown, -1 one to the left, 1 one to the right), on every frame of a swipe, its settle and Next. The library places slides with translate, so their transform is free: here each person turns by the progress times 90° around the edge they share with the next one, with a perspective on Lightbox.Viewport. The same variable makes a fade (opacity), a stack, or a coverflow.
Stories
From the people you follow.
3D coverflow#
The same variable makes a coverflow. neighbors={3} on Lightbox.Viewport renders three covers on each side, instead of one. The covers sit closer together than the viewport is wide, so --lightbox-swipe-distance on the viewport sets how far the finger travels for one cover, as any CSS length ([--lightbox-swipe-distance:calc(var(--cover-size)*2.2)]), and the covers keep up with the finger instead of lagging behind it. Slides placed side by side don't need it, even when the media is smaller than the viewport: the media moves with its slide. While you hold a cover, the viewport has data-swiping; raising it (data-swiping:z-10) draws the cover above its reflection and the controls.
The structure separates four jobs:
Lightbox.Slideplaces the cover in the fan and sorts it by depth. Placement stays outside perspective, so depth does not stretch the spacing.- An inner pose turns the cover in its own
perspective(), flattened into the slide. A smoothstep curve eases the turn into each wing. The slides stay parallel, so covers overlap without cutting through each other, including halfway through a swipe. - A surface inside the pose handles the pull and the exit. While the cover shown is pulled to dismiss, the others follow it a little (
--lightbox-dismiss-movement-xand-y), shrink slightly and dim with--lightbox-dismiss-progress. Ondata-ending-style, while the activeLightbox.Mediaflies back to its trigger, they fly there too: each cancels its own distance from the centre, moves by--lightbox-flight-movement-xand-y, and scales down to almost nothing from--lightbox-flight-scale, fading out in 60% of--lightbox-flight-duration. The ambient background, captions and controls leave on the same timing. Do not transform or fade the active surface or the whole popup: that would move or fade its return flight too. - A decorative, masked image below the media supplies the reflection without a browser-specific property. It fades on close and zoom. It is outside
Lightbox.Media, so it never becomes part of the flying image.
The cover size, perspective and depth scale together on smaller screens. The viewport fills the viewer; slide padding sizes and positions the resting covers. This keeps zoom and pan constrained by the viewer edges rather than the small cover box, avoiding empty strips when the enlarged image can fill that axis. While zoomed, neighboring covers and reflections fade away; keep the media's overflow visible so the enlarged image is not clipped. Put the ring and shadow on the image, not Lightbox.Media: zoom and pan transform only the image, so a frame on the media box would stay behind. The image inherits the media's rounded corners, including during zoom and the opening and closing flights. Reduced motion uses a flat carousel without reflections or entrance/exit movement.
Northern Lights
Aurora Field
Low Tide
The Undertow
Long Exposure
Night Shift
Ember
Saltwater Kids
Red Rock
Desert Lines
Above the Fog
Morning Choir
Open Water
Blue Hour
Base Camp
High Country
Coast Road
The Wanderers
Back Alley
Brick & Mortar
3D models#
An armory of live 3D weapons, rendered with Google's <model-viewer> and grouped by class. A weapon turns on its tile under the pointer, and they all open in one viewer: drag to rotate the weapon shown, and move between them with the arrows, the cards or ← →. The cards are grouped by class too, and the indicator, corner brackets with a lit bar, glides along with the slides. Escape or the close button flies the weapon back to its tile, turned the way you left it.
Lightbox.Media takes any content, so the model's element goes inside it with a width and height for its shape. Only images zoom, so the zoom gestures leave it alone. Its drags would normally swipe or pull the viewer closed: mark the element with Base UI's data-base-ui-swipe-ignore and the viewer leaves every press on it to the model. Set it to "x" or "y" to keep only the drags along that axis, so a drag along the other one still swipes or pulls, as in a Base UI drawer.
The viewer carries on from the tile, mid-turn and mid-animation, because it shows the same <model-viewer> element: the demo creates one per model outside React and moves it between its tile and its slide. Nothing is copied or loaded twice.
- Ready before it flies. Before it opens, the viewer is mounted out of sight. A ref callback on each slide moves its model in, and
onOpenChangereturns a Promise that waits until the model shown has drawn itself at the viewer's size. Only then does it fly, so its first frame is already sharp and the right size. - Never an empty tile. A weapon that leaves its tile leaves a still of itself, from
toDataURL(), in its place. When the viewer closes, the still is taken again, so the tile shows the model as it was turned. Once the model is back and has drawn, the still goes. The thumbnails are stills too. - Smooth flights. Every model holds still during the flights, so it isn't redrawn on every frame of them. Once the viewer has landed, only the model shown moves.
- The angle you left it at. On its own, a moved
<model-viewer>goes back to itscamera-orbit. The demo writes the current orbit into it before each move.
Armory
11 weaponsPrimary · Assault rifle
MDR
Assault rifle
Primary · Submachine gun
MPX
Submachine gun
MPSD
Submachine gun
Primary · Marksman rifle
MK14
Marksman rifle
M21 EBR
Marksman rifle
Primary · Sniper rifle
SRSA1
Sniper rifle
Precision rifle
Sniper rifle
Secondary · Sidearm
P320
Sidearm
P226
Sidearm
Revolver
Sidearm
Secondary · Launcher
RPG
Launcher
Models by austincford on Poly Pizza, CC BY 3.0.
The models are by austincford on Poly Pizza, under CC BY 3.0.
Controlled and changing items#
The viewer's state is two values, each of which you can control: whether it is open, with open and onOpenChange, and the item it shows, with value and onValueChange. Here they are shown in an address bar, as an app would put them in the URL. The value stays on the last item shown after the viewer closes, and is the first item until one is set. Changing value while the viewer is open slides to the new item, as Next does. A trigger calls onValueChange when it shows another item, then onOpenChange; handle.open(value) does the same.
items can change at any time, even while the viewer is open: it stays on the same item. Delete the open photo from the viewer and it moves to the neighbour; delete the last one and it closes. Add photos and the counter follows.
Album · 5 photos
Both callbacks also receive eventDetails, built like Base UI's: reason tells what made the change ("swipe", "trigger-press", "thumbnail-press", "escape-key", "close-press" …), event is the native event and trigger the element it came from. Call eventDetails.cancel() to stop it: a cancelled swipe springs back, and a cancelled pull to dismiss puts the image back. onOpenChangeComplete is called once the image has landed, in the viewer (true) or back on the page (false).
Loading and errors#
Before it opens, the viewer is mounted out of sight until the <img> or <video> in Lightbox.Media has loaded (a video, its size), so the image shows at once and flies at the right size. The trigger has data-pending meanwhile. The viewer opens anyway after LIGHTBOX_OPEN_TIMEOUT (3 s), still loading.
onOpenChange can return a Promise, and the viewer waits for it as well: here the slow photo waits 1.5 seconds more, as on a slow network. It waits up to 3 s before opening and 300 ms before closing (LIGHTBOX_CLOSE_TIMEOUT), then goes on. eventDetails.cancel() works until the wait ends, after an await too, and a cancelled change never happened: no onOpenChangeComplete, nothing to undo. A rejected Promise is logged, and the viewer goes on.
Inside the viewer, Lightbox.Media and its Lightbox.Slide have data-loading while the media loads and data-error if it fails, from the media's own events. Put a spinner or a message next to the media, inside Lightbox.Media: everything in it flies and follows the pull to dismiss. When the image fails, hide it (group-data-error:invisible): the box keeps its shape and the message takes its place. Give the <img> a new src to load it again. A failed image doesn't zoom, and the viewer keeps working.
- Unknown size. Without
widthandheight, the shape is only known once the image loads: fade it in withgroup-data-loading:opacity-0. - Retry. The fourth photo fails once and loads on retry. The message has
role="alert", so screen readers announce it. - Broken thumbnails. When a photo is gone, its thumbnail fails too. Base UI's
Avatarshows an icon in its place; itsdelaykeeps a thumbnail that loads quickly from flashing the icon.
Instant
Slow network
Unknown size
Fails once
Missing
Lightbox.Media only reads its direct <img>, <video> or <iframe> child (or the <img> of a <picture>), so an icon inside the message doesn't count. Content without one counts as loaded at once.
To show smaller images as thumbnails and larger ones in the viewer, give each <img> its own src or srcSet: the viewer waits for the one it shows.
Video and other media#
Lightbox.Media takes any media as its direct child: an <img>, a <video>, an <iframe>. It fits the media's shape inside the slide, flies it from the trigger and back, and follows the pull to dismiss. Only images zoom: on other media the zoom gestures do nothing and Lightbox.ZoomIn is disabled. A video pauses when its slide is no longer shown and when the viewer closes, and while a player with controls has focus, the arrow keys stay its own. Give the <video> preload="metadata": the viewer opens once it knows the video's size.
Give an embed its width and height (16 and 9). An iframe keeps every touch to itself, so swipe beside it or use Lightbox.Previous and Lightbox.Next.
Captions and animations#
Lightbox.Title and Lightbox.Description name and describe the viewer for screen readers. A function child receives the item shown, to build its title or caption; Lightbox.Description renders nothing when the result is empty. For text that belongs to a slide and moves with it, put a Lightbox.Caption in the slide: it describes its slide.
When the item shown changes, Lightbox.Title and Lightbox.Description (with a function child), Lightbox.Counter and Lightbox.Caption have data-starting-style for one frame, with data-activation-direction set to the side the new item came from: right after Next in a left-to-right layout, left after Previous. Style where the new text starts from, and a transition brings it in, as in the gallery at the top of the page. The element stays on screen while its text changes, so turn the transition off while the attribute is there (data-starting-style:transition-none): the text jumps to its start, then animates in.
Lightbox.Counter and Lightbox.ZoomValue take a function child, which receives their state, to format the position and the scale: {(state) => `${state.index + 1} of ${state.count}`}.
Toolbar#
The actions are plain buttons. To group them in a Base UI Toolbar, render each one as a Toolbar.Button. The lightbox part goes outside, so it keeps its disabled, and the toolbar keeps a disabled action focusable for its arrow keys. Set orientation="vertical" on Toolbar.Root for a column: ↑ and ↓ move between the actions.
Thumbnails#
Lightbox.ThumbnailList renders a Lightbox.Thumbnail per item inside the viewer, through a function child like Lightbox.Viewport. What's inside a thumbnail is yours: an image, a dot, a title. The list is a tab list and each thumbnail a tab, as in the WAI-ARIA tabbed carousel: the slides become their tab panels, and the thumbnail of the item shown has aria-selected and data-active.
- Keyboard. The list is a single tab stop. The arrow keys move between thumbnails and show their item, and Home and End go to the first and last. With
activateOnFocus={false}, the arrows only move focus and Enter shows the item. - Orientation.
orientationsets which arrow keys move and which way the list scrolls: ← → for"horizontal"(the default), ↑ ↓ for"vertical". The list hasdata-orientation, so one class list can style both. - Scrolling. Give the list
overflow: auto. The active thumbnail glides to its centre, and is centred again when a CSS transition changes its size. The mouse can drag the list (data-swipingmeanwhile), and the wheel scrolls a horizontal list. Touch scrolls natively. - Edges. The list has
data-overflow-startanddata-overflow-endwhile thumbnails are hidden past an edge: fade that edge with a mask,data-overflow-end:mask-r-from-85%. Hide the browser's scrollbar with[scrollbar-width:none] [&::-webkit-scrollbar]:hidden. - Sliding indicator.
Lightbox.ThumbnailIndicator, placed in the list after the function child, is one element that glides to the active thumbnail, like Base UI'sTabs.Indicator. Make the listrelative: the indicator gets the active thumbnail's box in it as--active-thumbnail-left,--active-thumbnail-top,--active-thumbnail-widthand--active-thumbnail-height, scrolls with the list, and follows thumbnails that change size. The variables move with the slides on every frame: towards the next or previous thumbnail while a swipe drags them, then along with them as they settle, at the speed of the flick, and the same for Previous, Next and the keys. So the indicator needs no transition: without one, it lands exactly when the slides do. A jump of several items (Home, End, a far thumbnail) starts the glide from the thumbnail next to the target.
Portfolio#
A list with a title per project beside the image on wide screens, and a strip of squares below it on phones: orientation follows a media query so the arrow keys match the layout, and the classes switch on data-orientation. The indicator is a soft highlight behind the active row, or a ring around the active square. A vertical toolbar zooms; the panorama goes to 800% with maxScale={8} (4 by default).
Switch to right to left: the next image is on the left, and the arrow keys and swipes follow. The viewer is portaled to the end of <body>, so it takes its own dir, and Base UI's DirectionProvider sets the direction of the lightbox parts.
Selected work
River valley
River valley between cliffs
A portrait, taller than wide
Portrait of a woman
A square image
Waterfall in a forest
A wide panorama
Panorama of a valley
Untitled
Rocky coast
Jellyfish
Jellyfish in dark water
Cropping in the viewer#
To crop the photo shown, lay a Cropper over the viewer inside Lightbox.Popup: see its photo editor demo.
Phone viewer#
The viewer works like the photo app of a phone:
- Swipe between images; the image follows the finger, carries on with the flick, and rubber-bands at the first and last image.
- Pinch to zoom around the fingers, and double tap to zoom in at the tapped point or back out.
- Pan a zoomed image; past its edge, the drag carries on to the next image.
- Pull down (or up) to dismiss: the image follows the finger and flies back to its thumbnail on release.
- Tap to hide or show the controls.
With a mouse or trackpad, drag swipes, double click zooms, and the wheel or a trackpad pinch zooms around the cursor.
Here the image fills the screen width under translucent bars. While the image is pulled to dismiss, --lightbox-dismiss-progress goes from 0 to 1 on the popup and the backdrop: the page fades in and un-blurs behind it, and the bars slide off the top and bottom of the screen. Try it on a phone, or with the browser's device toolbar.
Recents
8 photosStyling#
Every part is unstyled. Style the viewer with Tailwind (or any CSS), using the parts' data attributes and CSS variables:
Lightbox.Popuphasdata-zoomedwhile the image is zoomed,data-controls-hiddenafter a tap hid the controls, anddata-starting-style/data-ending-stylewhile the image flies in and out. Fade the controls, not the popup: the image is inside it. From a control, read them within-data-*:in-data-controls-hidden:opacity-0. ItsclassName,styleandrenderfunctions receivezoomed,controlsHiddenandactivationDirectionwith the dialog's state.Lightbox.Popup,Lightbox.Viewport,Lightbox.Slideand the parts that show the item havedata-activation-direction(left,right, ornonewhen the viewer opens): the side the item shown came from, mirrored in a right-to-left layout.Lightbox.Popupgets--lightbox-swipe-movement-x(px on screen, negative to the left) and--lightbox-swipe-progress(the same as a share of a slide,-1to1) while a swipe drags the slides and while they settle, like Base UI's drawer swipe variables. They are0at rest. Move anything with the swipe:translate-x-[calc(var(--lightbox-swipe-progress,0)*2rem)]. They are relative to the item shown, so they jump when it changes at the end of a swipe.Lightbox.Popupgets--lightbox-swipe-strengthwhen a swipe is released:0.1for a hard flick to1for a slow release, like Base UI's drawer. The slides settle in 300 ms times it, and go back to1once settled: scale a transition that follows them,duration-[calc(300ms*var(--lightbox-swipe-strength,1))].Lightbox.Popupgets--lightbox-zoom-scale(1at rest) and--lightbox-zoom-progress(0at rest,1at the largest scale), to fade or move things as the image zooms.Lightbox.PopupandLightbox.Backdropget--lightbox-flight-durationand--lightbox-flight-easing: the length and curve of the flight the image is on. After a pull to dismiss, the curve is a spring (a CSSlinear()) that starts at the speed of the finger. Time the backdrop and the controls with them,duration-(--lightbox-flight-duration) ease-(--lightbox-flight-easing), and they arrive with the image.Lightbox.Popupgets--lightbox-flight-movement-xand--lightbox-flight-movement-y(px) and--lightbox-flight-scaleat the start of each flight: how far from where it rests in the viewer the image flies, to the trigger it lands on when the viewer closes, or from the trigger it leaves when it opens, and its size there as a share of its size in the viewer. They are0pxand1before any flight. Send other content to the same place:[translate:var(--lightbox-flight-movement-x)_var(--lightbox-flight-movement-y)] scale-(--lightbox-flight-scale)ondata-ending-style, timed with--lightbox-flight-duration, as the coverflow does.Lightbox.PopupandLightbox.Backdropget--lightbox-dismiss-movement-xand--lightbox-dismiss-movement-y(px) with--lightbox-dismiss-progress(0 to 1) while the image is pulled to dismiss, and havedata-swipingduring a drag and until the image settles back. Turn transitions off then (data-swiping:transition-none, orin-data-swiping:transition-noneon a control), or whatever follows the progress lags behind the finger.Lightbox.Viewporthasdata-swipingduring a gesture. Give it a size (absolute inset-0) and set the space between images withgap-x-4. Nothing clips: the next images, the zoomed image and the image pulled to dismiss are drawn past its edges, off screen in a full-screen viewer. In a smaller one, addoverflow-hidden; leave it off for the media to leave the viewport, like the stories. One slide of finger travel is its width plus that gap; when the CSS draws the slides closer together, set--lightbox-swipe-distanceon it (any CSS length) so they keep up with the finger, as the coverflow does.Lightbox.Slidehas--lightbox-slide-progress: its distance from the centre in slides, on every frame the slides move. It is placed withtranslate, leavingtransformfree for a custom transition (a cube, a fade, a stack); put aperspectiveonLightbox.Viewportfor 3D.Lightbox.Slide's padding is the space around the media:p-0for edge to edge on phones,md:px-24on larger screens. Addpt-[env(safe-area-inset-top)]and the like for notches (withviewport-fit=coveron the page). It hasdata-active, anddata-loadinganddata-errorfor its media.Lightbox.Mediasets its own position and size, and the size of the media in it: style what's around the media, not its box.Lightbox.Triggerhasdata-popup-openwhile the image will fly back to it, from the opening flight to the end of the closing one: the trigger of the item shown, or the onecloseTargetpicks. Hide it withdata-popup-open:invisible: the thumbnail disappears from the page but keeps its place, and comes back when the image lands. It also hasdata-pendingwhile the viewer waits to open from it, anddata-flyingwhile the image flies from it or to it.
Keyboard#
| Key | Action |
|---|---|
| ← → | Previous / next image, or pan while zoomed |
| ↑ ↓ | Pan while zoomed |
| Home / End | First / last image |
| + − | Zoom in / out |
| 0 | Reset the zoom |
| Esc | Close; the image flies back to its thumbnail |
Inside a widget that uses the arrow keys itself, they stay that widget's: a Base UI Toolbar, Lightbox.ThumbnailList, a slider, a text field, a video player.
API reference#
Lightbox.Root#
Holds the lightbox state: the items, the item shown, whether the viewer is
open, its animation, and the zoom of the active image. Place the viewer
parts inside it, and the triggers inside it or anywhere else with a
handle. Before the viewer opens, it is mounted out of sight until its
Lightbox.Media has loaded, so the image flies from its trigger at the
right size; the trigger has data-pending meanwhile.
Renders no element.
Props
- items*
readonly Item[]The items, in order. They can have any shape.
- itemToValue
(item: Item) => LightboxItemValuedefault(item) => item.idReturns the value that identifies an item. It must stay the same when items are added, removed or reordered.
- children
React.ReactNode- open
booleanWhether the viewer is open.
- defaultOpen
booleandefaultfalseWhether the viewer is open at first, when
openis not controlled.- onOpenChange
( open: boolean, eventDetails: LightboxRootChangeEventDetails, ) => void | Promise<void>Called when the viewer is about to open or close, with why (
eventDetails.reason). CalleventDetails.cancel()to keep it as it is: a cancelled pull to dismiss springs back. Return a Promise to make the viewer wait for it: up toLIGHTBOX_OPEN_TIMEOUT(3 s) before it opens,LIGHTBOX_CLOSE_TIMEOUT(300 ms) before it closes; it goes on after that.cancel()works until the wait ends, after anawaittoo. A rejected Promise is logged, and the viewer goes on.- onOpenChangeComplete
(open: boolean) => voidCalled when the viewer has finished opening or closing: the image has landed in the viewer, or back on the page.
- value
LightboxItemValueValue of the item shown in the viewer. It stays on the last item shown after the viewer closes.
- defaultValue
LightboxItemValuedefaultthe first item's valueValue of the item shown at first, when
valueis not controlled.- onValueChange
( value: LightboxItemValue, eventDetails: LightboxRootChangeEventDetails, ) => voidCalled when the viewer is about to show another item, with why (
eventDetails.reason), and before it opens on an item that isn't the one shown. CalleventDetails.cancel()to keep the current item: a cancelled swipe springs back.- closeTarget
"active" | "opener" | "nearest"default"active"Where the image flies back to when the viewer closes, and where focus returns. When that trigger shows another image, the image turns into it on the way, and the trigger stays empty while the viewer is open.
"active"default The trigger of the item shown. Without one on the page, the image fades out. For a grid with a trigger per item."opener"The trigger that opened the viewer, whichever item is shown. For one cover that opens a whole album."nearest"The trigger of the item shown, or else the trigger of the nearest item that has one. For a grid that shows a few items and a "+5" tile.
- scrollToTrigger
booleandefaulttrueScrolls the page, hidden behind the viewer, to keep the trigger the image flies back to in view as the items change. Without it, closing on an item whose trigger is off screen fades the image out instead.
- handle
LightboxHandleLinks
Lightbox.Triggers placed outside the root, fromLightbox.createHandle().- maxScale
numberdefault4Largest zoom scale.
Lightbox.Trigger#
Opens the viewer on one item. The image flies from the trigger to the
viewer and back. Put the thumbnail inside it, or lay it over the thumbnail.
It has data-popup-open while the image will fly back to it (the item
shown is its own, or closeTarget picks it): hide it then, keeping its
space, so the image always lands in an empty slot. When value matches no item, only the children render,
without the button. Renders a <button> element.
Props
- value*
LightboxItemValueValue of the item it opens, as returned by
itemToValue.- handle
LightboxHandleLinks a trigger placed outside
Lightbox.Rootto it, fromLightbox.createHandle().- crossfade
booleandefaultfalseWhether it shows something other than the media it opens: an avatar, a cover, an icon. The image then crossfades with a copy of it during the flights, growing out of its look and turning back into it on the way home. A trigger
closeTargetpicks for another item always crossfades.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-pending
- Present while its image loads before the viewer opens.
- data-flying
- Present while the image flies from it or back to it, even another item's image that
closeTargetsends here. Hide the trigger meanwhile: the image lands in an empty slot. - data-crossfade
- Present with
crossfade: it shows something other than the media it opens, and the image crossfades with a copy of it during the flights. - data-disabled
- Present while another trigger's image loads.
- data-popup-open
- Present on the trigger the image will fly back to: the one of the item shown, or the one
closeTargetpicks, such as a "+5" tile. From the start of the opening flight to the end of the closing one. Hide the thumbnail meanwhile and keep its space (visibility: hidden), like a phone's photo app: the image always lands in an empty slot.
Lightbox.Portal#
Moves the viewer to the end of <body> while it is open.
Renders a <div> element.
Props
- keepMounted
booleandefaultfalseWhether to keep the portal mounted in the DOM while the popup is hidden.
- container
HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | nullA parent element to render the portal element into.
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Lightbox.Backdrop#
Covers the page behind the viewer. While the image is pulled to dismiss, it
gets --lightbox-dismiss-progress (0 to 1), to fade it out:
opacity: calc(1 - var(--lightbox-dismiss-progress, 0)). While the image
flies back, it has data-ending-style like any Base UI popup. It has
data-swiping during a drag: turn its transition off then, or the fade
lags behind the finger.
Renders a <div> element.
Props
- forceRender
booleandefaultfalseWhether the backdrop is forced to render even when nested.
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-ending-style
- Present while the image flies back or fades out.
- data-swiping
- Present while a finger or the mouse swipes or pulls the image, and until it settles back after a pull to close. Turn transitions off meanwhile, so what follows
--lightbox-dismiss-progresskeeps up with the finger.
CSS variables
- --lightbox-flight-duration
- Length of the flight the image is on, or was last on:
350ms. Fade the backdrop over it:transition-duration: var(--lightbox-flight-duration).CSS - --lightbox-flight-easing
- Curve of that flight: a
cubic-bezier(), or alinear()spring after a pull to dismiss.transition-timing-function: var(--lightbox-flight-easing).CSS - --lightbox-dismiss-progress
- Progress of the pull to dismiss: 0 at rest, 1 at the closing distance. Fade with
opacity: calc(1 - var(--lightbox-dismiss-progress, 0)).number - --lightbox-dismiss-movement-x
- How far the image is pulled to dismiss, from where it rests.CSS
- --lightbox-dismiss-movement-y
- How far the image is pulled to dismiss, from where it rests.CSS
Lightbox.Popup#
The full-screen viewer. Place Lightbox.Viewport and the controls inside.
It takes focus when it opens, so its keys work at once; Tab moves on to
the controls. Pass initialFocus to focus another element.
Keys: ← → previous / next (pan while zoomed), ↑ ↓ pan, Home / End first /
last, + − zoom, 0 reset zoom, Esc close. Inside a widget that uses these
keys itself (a Base UI Toolbar, Lightbox.ThumbnailList, a slider, a text
field), they stay that widget's.
Has data-zoomed while the image is zoomed, data-controls-hidden after a
tap hides the controls, and data-ending-style while the image flies back
(fade the controls then, not the popup: the image is inside it). Gets
--lightbox-dismiss-progress while the image is pulled to dismiss,
--lightbox-swipe-movement-x and --lightbox-swipe-progress while a swipe
moves the slides, and has data-swiping during a drag: turn transitions
off then.
Renders a <div> element.
Props
- initialFocus
boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void)Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. -
false: Do not move focus. -true: Move focus based on the default behavior (first tabbable element or popup). -RefObject: Move focus to the ref element. -function: Called with the interaction type (mouse,touch,pen, orkeyboard). Return an element to focus,trueto use the default behavior,nullto fall back to the default behavior, orfalse/undefinedto do nothing.- finalFocus
boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void)Determines the element to focus when the dialog is closed. -
false: Do not move focus. -true: Move focus based on the default behavior (trigger or previously focused element). -RefObject: Move focus to the ref element. -function: Called with the interaction type (mouse,touch,pen, orkeyboard). Return an element to focus,trueto use the default behavior,nullto fall back to the default behavior, orfalse/undefinedto do nothing.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-zoomed
- Present while the active image is zoomed in.
- data-controls-hidden
- Present after a tap hid the controls (toolbar, caption, close button).
- data-ending-style
- Present while the image flies back or fades out. Fade the controls then, not the popup: the image is inside it.
- data-swiping
- Present while a finger or the mouse swipes or pulls the image, and until it settles back after a pull to close. Turn transitions off meanwhile, so what follows
--lightbox-dismiss-progresskeeps up with the finger. - data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none'
CSS variables
- --lightbox-flight-duration
- Length of the flight the image is on, or was last on:
350ms. Time a transition that goes with it (the backdrop, the controls) with it:transition-duration: var(--lightbox-flight-duration).CSS - --lightbox-flight-easing
- Curve of that flight: a
cubic-bezier(), or alinear()spring that starts at the speed of the pull that dismissed the viewer. Use it with--lightbox-flight-durationso the transition and the image arrive together.CSS - --lightbox-flight-movement-x
- How far the image flies from where it rests in the viewer, horizontally: to the trigger it lands on when the viewer closes, from the trigger it leaves when it opens. Set at the start of each flight. Move other content along with the image:
translate: var(--lightbox-flight-movement-x) var(--lightbox-flight-movement-y), with--lightbox-flight-duration.CSS - --lightbox-flight-movement-y
- The same, vertically.CSS
- --lightbox-flight-scale
- The image's size at that trigger, as a share of its size in the viewer:
0.2when it lands on a tile a fifth as wide.1before any flight.number - --lightbox-dismiss-progress
- Progress of the pull to dismiss: 0 at rest, 1 at the closing distance. Fade with
opacity: calc(1 - var(--lightbox-dismiss-progress, 0)).number - --lightbox-swipe-movement-x
- How far the slides have moved from the one shown, on screen: negative to the left. Set while a swipe drags them and while they settle,
0pxat rest.CSS - --lightbox-swipe-progress
- The same as a share of one slide:
0at rest,1a whole slide to the right,-1to the left. Move things with the swipe:translate: calc(var(--lightbox-swipe-progress, 0) * 2rem) 0.number - --lightbox-swipe-strength
- How hard a released swipe was flicked,
0.1to1(a slow release), like Base UI's drawer: the slides settle in 300ms times it. Scale a transition that follows them:transition-duration: calc(300ms * var(--lightbox-swipe-strength, 1)).number - --lightbox-dismiss-movement-x
- How far the image is pulled to dismiss, from where it rests.CSS
- --lightbox-dismiss-movement-y
- How far the image is pulled to dismiss, from where it rests.CSS
- --lightbox-zoom-scale
- Scale of the active image:
1at rest.number - --lightbox-zoom-progress
- The zoom from rest to the largest scale:
0to1. Fade the controls as the image zooms in:opacity: calc(1 - var(--lightbox-zoom-progress, 0)).number
Lightbox.Title#
Names the viewer for screen readers. Hide it visually if the design has no
visible title. With a function child, it names the item shown, and has
data-starting-style for a frame when the item changes, with
data-activation-direction, to animate the new title in.
Renders an <h2> element.
Props
- children
React.ReactNode | ((item: any) => React.ReactNode)Content. A function receives the item shown in the viewer, as passed to
items, to name it:{(photo) => photo.title}.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none' - data-starting-style
- Present for one frame when the item shown changes, with a function child: style where the new content starts from, with transitions off (
data-starting-style:transition-none), and a transition animates it in.
Lightbox.Description#
Describes the item shown in the viewer, such as its caption, linked to the
viewer for screen readers. With a function child, it has
data-starting-style for a frame when the item changes, with
data-activation-direction, to animate the new caption in. Renders
nothing when its content is empty.
Renders a <p> element.
Props
- children
React.ReactNode | ((item: any) => React.ReactNode)Content. A function receives the item shown in the viewer, as passed to
items, to show its caption:{(photo) => photo.caption}.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none' - data-starting-style
- Present for one frame when the item shown changes, with a function child: style where the new content starts from, with transitions off (
data-starting-style:transition-none), and a transition animates it in.
Lightbox.Viewport#
The window the slides swipe through: a carousel region that handles the
gestures (swipe, pinch, double tap, pan, pull to dismiss, wheel zoom). Give
it a size (usually position: absolute; inset: 0 in the popup), and set
the space between slides with column-gap (gap-4).
One slide of finger travel is the viewport's width plus that gap. When the
CSS draws the slides closer together, as a coverflow does, set
--lightbox-swipe-distance on it so they follow the finger.
It doesn't clip: the slides next to the one shown, the zoomed image and the
media pulled to dismiss are drawn past its edges. In a viewer smaller than
the screen, clip them with overflow: hidden; leave it off for a pulled
card or a 3D transition to leave the viewport.
A press that starts on a control inside a slide (a button, a link, a
player) is left to that control, and so is one on content marked with
Base UI's data-base-ui-swipe-ignore, such as a 3D model that rotates
with a drag: with no value, all its drags; with "x" or "y", only
those along that axis. It has data-swiping while a finger or
the mouse drags, and data-activation-direction after the item shown
changes.
Renders a <div> element.
Props
- children*
(item: any, index: number) => React.ReactNodeRenders one item: a
Lightbox.Slidewith aLightbox.Imageor other content inside. Receives the item as passed toitems. Called only for the active item and itsneighbors, and the items a swipe brings into view.- neighbors
numberdefault1Items rendered on each side of the one shown. Raise it for a transition that shows more than the next item, like a coverflow; the others stay empty until they come near. While swiping, items around the visible part of the track are also rendered.
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-zoomed
- Present while the active image is zoomed in.
- data-swiping
- Present while a finger or the mouse swipes, pans or pulls to dismiss.
- data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none'
CSS variables
- --lightbox-swipe-distance
- How far the finger travels to move one slide: any CSS length. Set it yourself on the viewport; the viewport reads it. By default, one slide is the viewport's width plus its
column-gap. Set it when the CSS draws the slides closer together than that, as a coverflow does, so they follow the finger instead of lagging behind it:[--lightbox-swipe-distance:calc(var(--cover-size)*0.68)].CSS
Lightbox.Slide#
One slide of the viewport, as large as the viewport. Return it from the
function passed to Lightbox.Viewport. Its padding is the space kept
around the media (p-6 md:p-24, or p-0 for edge to edge). Put a
Lightbox.Media inside, and a Lightbox.Caption, or any other content.
It has --lightbox-slide-progress, its distance from the centre, and it
is placed with translate, so its transform is free for a custom
transition between slides.
With a Lightbox.ThumbnailList, it is the tab panel of its thumbnail.
Renders a <div> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-active
- Present on the slide shown in the viewer.
- data-loading
- Present while its
Lightbox.Medialoads. - data-error
- Present when its
Lightbox.Mediafailed to load. - data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none'
CSS variables
- --lightbox-slide-progress
- How far the slide is from the centre of the viewport, in slides, on screen:
0when it is the one shown and at rest,-1one slide to the left,1one to the right, in between while a swipe, its settle or Next moves the slides. The slide is placed withtranslate, so itstransformis free for a custom transition, such as a cube:transform: rotateY(calc(var(--lightbox-slide-progress) * 90deg))with aperspectiveonLightbox.Viewport.number
Lightbox.Media#
The media of a Lightbox.Slide: put an <img>, a <video> or an
<iframe> inside, as its direct child. It is a box with the media's
shape, fitted inside the slide's padding, that the media fills. The box
flies from the trigger and back and follows the pull to dismiss, with all
of its content: put a spinner or an error message next to the media and
show them with the slide's data-loading and data-error. An <img>
also zooms and pans; other media don't. A <video> pauses when its
slide is no longer shown. The loading state comes from the
media's own events. Its position and size are set by the box, and its
translate and scale while it is pulled to dismiss.
Renders a <div> element.
Props
- width
number | stringWidth of the media, when known. With
height, it sets the box's shape, before the media has loaded too, so the image flies in at the right size. Without both, the box takes the shape of the loaded media. A shape other than the media's keeps every slide the same: fit the media in it withobject-fit.- height
number | stringHeight of the media, when known. See
width.- fill
booleandefaultfalseWhether the box fills the slide, inside its padding, whatever the shape of the media:
width,heightand the loaded size don't change it. Size the slide with CSS, and fit the media in it withobject-fit.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-active
- Present on the media of the slide shown in the viewer.
- data-loading
- Present while the media loads.
- data-error
- Present when the media failed to load.
Lightbox.Caption#
The text of a Lightbox.Slide, such as its title or credits. Place it in
the slide, next to Lightbox.Media: it moves with the slide, and
describes it for screen readers. It has data-starting-style for a frame
when its slide becomes the one shown, with data-activation-direction, to
animate it in. Renders nothing when its content is empty.
Renders a <div> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-active
- Present when its slide is the one shown in the viewer.
- data-activation-direction
- Side of the previous item the item shown came from, on screen.'left' | 'right' | 'none'
- data-starting-style
- Present for one frame when its slide becomes the one shown: style where the caption starts from, with transitions off (
data-starting-style:transition-none), and a transition animates it in.
Lightbox.Previous#
Shows the previous item. Disabled on the first one.
Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-disabled
- Present when the action can't run now.
Lightbox.Next#
Shows the next item. Disabled on the last one.
Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-disabled
- Present when the action can't run now.
Lightbox.Counter#
Position of the active item, as "2 / 5". Announced to screen readers when
it changes. It has data-starting-style for a frame when the item
changes, with data-activation-direction, to animate the new number in.
Renders a <span> element.
Props
- children
React.ReactNode | ((state: LightboxCounterState) => React.ReactNode)default`${index + 1} / ${count}`Content. A function receives the state, to format it.
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-activation-direction
- Side of the previous item the item shown came from, on screen:
rightafter Next in a left-to-right layout.nonewhen the viewer opens.'left' | 'right' | 'none' - data-starting-style
- Present for one frame when the item shown changes: style where the new content starts from, with transitions off (
data-starting-style:transition-none), and a transition animates it in.
Lightbox.ZoomIn#
Zooms the active image in by one step. Disabled at the largest scale.
Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-disabled
- Present when the action can't run now.
Lightbox.ZoomOut#
Zooms the active image out by one step. Disabled when it isn't zoomed.
Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-disabled
- Present when the action can't run now.
Lightbox.ZoomReset#
Puts the active image back at its original size. Disabled when it isn't
zoomed. Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-disabled
- Present when the action can't run now.
Lightbox.ZoomValue#
Zoom level of the active image, as a percentage.
Renders a <span> element.
Props
- children
React.ReactNode | ((state: LightboxZoomValueState) => React.ReactNode)default`${Math.round(scale * 100)}%`Content. A function receives the state, to format the scale.
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Lightbox.Close#
Closes the viewer; the image flies back to its trigger.
Renders a <button> element.
Props
- nativeButton
booleandefaulttrueWhether the component renders a native
<button>element when replacing it via therenderprop. Set tofalseif the rendered element is not a button (for example,<div>).- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Lightbox.ThumbnailList#
A strip of thumbnails inside the viewer, one per item. It is a single tab
stop: the arrow keys move between thumbnails and show their item, Home and
End go to the first and last. It scrolls itself: the active thumbnail
glides to its centre, the mouse can drag it, and the wheel scrolls a
horizontal strip. Make it scroll with overflow: auto, and hide its
scrollbar in CSS if you like. Follows the WAI-ARIA tabbed carousel
pattern: it is a tab list, and the slides are its tab panels.
Renders a <div> element.
Props
- children*
| LightboxThumbnailRender | ReadonlyArray<LightboxThumbnailRender | React.ReactNode>A function that renders one thumbnail: a
Lightbox.Thumbnailwith an image inside. Receives the item as passed toitems, and is called for every item. Other children, such as aLightbox.ThumbnailIndicator, render after the thumbnails.- orientation
"horizontal" | "vertical"default"horizontal"Which arrow keys move between thumbnails, and which way the strip scrolls. Set as
data-orientation."horizontal"default Left to right (right to left in a right-to-left layout); ← → move, and the wheel scrolls the strip sideways."vertical"Top to bottom; ↑ ↓ move.
- activateOnFocus
booleandefaulttrueWhether the arrow keys also show the item of the thumbnail they move to. When
false, they only move focus, and Enter shows the item.- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-orientation
- Whether the thumbnails run left to right or top to bottom.'horizontal' | 'vertical'
- data-overflow-start
- Present while thumbnails are scrolled out of view before the start: fade that edge with a mask.
- data-overflow-end
- Present while thumbnails are scrolled out of view past the end.
- data-swiping
- Present while the mouse swipes the strip. Turn off snapping and transitions meanwhile.
Lightbox.Thumbnail#
Shows its item in the viewer. Return it from the function passed to
Lightbox.ThumbnailList, with the thumbnail image inside, such as a Base UI
Avatar that handles loading and errors, or anything else, such as a dot.
It is a tab: it has aria-selected and data-active while its item is
shown, and the list keeps it centred.
Renders a <button> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-active
- Present on the thumbnail of the item shown in the viewer.
Lightbox.ThumbnailIndicator#
Marks the active thumbnail with one element that moves from thumbnail to
thumbnail, like Base UI's Tabs.Indicator. Place it in
Lightbox.ThumbnailList, after the function child, and make the list
position: relative: it gets the active thumbnail's box in the list as
CSS variables, and scrolls with the list. They move with the slides on
every frame (a swipe, its settle, Next), so style it with them and no
transition: left: var(--active-thumbnail-left). Hidden from screen readers; renders
nothing until it has measured a thumbnail.
Renders a <span> element.
Props
- className
stringCSS class applied to the element.
- style
React.CSSPropertiesStyle applied to the element.
- render
React.ReactElement | ((props, state) => React.ReactElement)Replaces the element with another tag or component, or composes it with one. Takes an element, or a function that receives the props and the state.
Data attributes
- data-activation-direction
- Side of the previous item the item shown came from, on screen.'left' | 'right' | 'none'
CSS variables
- --active-thumbnail-left
- Distance from the left of the thumbnail list's content to the active thumbnail. While a swipe drags the slides, it moves towards the next or previous thumbnail with them.number
- --active-thumbnail-top
- Distance from the top of the thumbnail list's content to the active thumbnail.number
- --active-thumbnail-width
- Width of the active thumbnail.number
- --active-thumbnail-height
- Height of the active thumbnail.number
LightboxMediaStatusunion#
Loading state of the media in a Lightbox.Slide.
Values
- "idle"
No
Lightbox.Mediain the slide.- "loading"
The media is loading; the slide and the media have
data-loading.- "loaded"
The media has loaded, or has nothing to load.
- "error"
The media failed to load; the slide and the media have
data-error.
LightboxRootActivationDirectionunion#
Side of the previous item the item shown came from, on screen. Set as
data-activation-direction, like Base UI's tabs.
Values
- "none"
The viewer just opened, or the item shown didn't move.
- "left"
It came from the left: after Previous in a left-to-right layout, Next in a right-to-left one.
- "right"
It came from the right: after Next in a left-to-right layout, Previous in a right-to-left one.
LightboxRootChangeEventDetailstype#
Details of a change: its reason, the native event, and the element it
came from (trigger). Call cancel() to stop it; a cancelled swipe or
pull to dismiss springs back.
Properties
- reason*
"trigger-press" | "swipe" | "keyboard" | "list-navigation" | "thumbnail-press" | "previous-press" | "next-press" | "close-press" | "escape-key" | "outside-press" | "focus-out" | "imperative-action" | "missing" | "none"The reason for the event.
- event*
ReasonToEvent<Reason>The native event associated with the custom event.
- cancel*
() => voidCancels Base UI from handling the event.
- allowPropagation*
() => voidAllows the event to propagate in cases where Base UI will stop the propagation.
- isCanceled*
booleanIndicates whether the event has been canceled.
- isPropagationAllowed*
booleanIndicates whether the event is allowed to propagate.
- trigger*
Element | undefinedThe element that triggered the event, if applicable.
LightboxRootChangeEventReasonunion#
Why the viewer opened or closed, in onOpenChange, or showed another
item, in onValueChange. The values are Base UI's where it has one
(escape-key, close-press), so a reason compares with the same string
as in a Base UI dialog.
Values
- "trigger-press"
A
Lightbox.Triggerwas pressed.- "swipe"
A swipe to the next or previous item, or a pull to dismiss.
- "keyboard"
A viewer shortcut: ← → Home End.
- "list-navigation"
The arrow keys in a
Lightbox.ThumbnailListwithactivateOnFocus.- "thumbnail-press"
A
Lightbox.Thumbnailwas pressed.- "previous-press"
Lightbox.Previouswas pressed.- "next-press"
Lightbox.Nextwas pressed.- "close-press"
Lightbox.Closewas pressed.- "escape-key"
Escape was pressed.
- "outside-press"
A press outside the popup.
- "focus-out"
Focus left the popup.
- "imperative-action"
A call on the
handle:open()orclose().- "missing"
The open item was removed from
items: the viewer shows its neighbour, or closes.- "none"
Another cause, such as a Base UI dialog change without a reason.
LightboxRootCloseTargetunion#
Where the image flies back to when the viewer closes, and where focus returns.
Values
- "active"
The trigger of the item shown. Without one on the page, the image fades out. For a grid with a trigger per item.
- "opener"
The trigger that opened the viewer, whichever item is shown. For one cover that opens a whole album.
- "nearest"
The trigger of the item shown, or else the trigger of the nearest item that has one. For a grid that shows a few items and a "+5" tile.
LightboxRootPhaseunion#
Where the viewer is in its open and close animation.
Values
- "closed"
The viewer is not shown.
- "opening"
The image flies from its trigger, or fades in.
- "open"
The image has landed in the viewer.
- "closing"
The image flies back to its trigger, or fades out.
LightboxThumbnailListOrientationunion#
Which way the thumbnails run, and which arrow keys move between them. Set
as data-orientation.
Values
- "horizontal"
Left to right (right to left in a right-to-left layout); ← → move, and the wheel scrolls the strip sideways.
- "vertical"
Top to bottom; ↑ ↓ move.
LightboxItemValuetype#
Identifies an item, as returned by itemToValue: stable when items are
added, removed or reordered.
Properties
- toString*
- valueOf*
- toLocaleString*
LightboxMediaSizetype#
Natural size of a media element, in px.
Properties
- width*
number- height*
number