# Lightbox

> A full-screen viewer over the scrim for images and attachment previews, with previous and next, thumbnails and one button to close.

Source: https://raft-design.botiverse.build/components/lightbox. Generated from the current release; where it differs from what you remember, this page is right.

A lightbox shows an image or a file preview at full size over the whole window: the image on a full-screen `layer-backdrop`, previous and next, and one icon button to close ([Built from existing parts](https://raft-design.botiverse.build/components.md#file-viewer)). Use it when someone opens an image or an attachment from a message. It is for media and previews only.

```tsx
import { Download, X } from "lucide-react";
import {
  Button,
  Lightbox,
  LightboxClose,
  LightboxContent,
  LightboxInfoActions,
  LightboxInfoBar,
  LightboxInfoDock,
  LightboxInfoTitle,
  LightboxMedia,
  LightboxStage,
  LightboxTitle,
  LightboxTrigger,
} from "@botiverse/raft-ui";

const DEMO_IMAGE_SRC =
  "https://images.unsplash.com/photo-1518005020951-eccb494ad742?auto=format&fit=crop&w=1440&q=80";

export function ImageLightboxShellComposition() {
  return (
    <Lightbox>
      <LightboxTrigger render={<Button type="button" variant="default" />}>
        Open image lightbox
      </LightboxTrigger>
      <LightboxContent data-testid="image-lightbox">
        <LightboxTitle className="sr-only">product-shot.png</LightboxTitle>
        <LightboxClose aria-label="Close">
          <X />
        </LightboxClose>
        <LightboxStage>
          <LightboxMedia render={<img src={DEMO_IMAGE_SRC} alt="Product attachment" />} />
          <LightboxInfoDock>
            <LightboxInfoBar>
              <LightboxInfoTitle>
                release-card-final-approved-for-directory-cover.png
              </LightboxInfoTitle>
              <LightboxInfoActions>
                <Button type="button" variant="ghost" size="icon-md" aria-label="Download">
                  <Download />
                </Button>
              </LightboxInfoActions>
            </LightboxInfoBar>
          </LightboxInfoDock>
        </LightboxStage>
      </LightboxContent>
    </Lightbox>
  );
}
```

## When to use

| Situation                                                           | Use                                                              |
| ------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Viewing an image, a screenshot or a video at full size              | Lightbox, image shell                                            |
| Previewing an attached file (an HTML page, a document) at full size | Lightbox, attachment shell                                       |
| A decision, a form or anything that is not media                    | [Dialog](https://raft-design.botiverse.build/components/dialog.md)                                    |
| Details people read beside the page                                 | [Drawer](https://raft-design.botiverse.build/components/drawer.md)                                    |
| The attachment in the message itself                                | [MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md) |

## Anatomy

- `Lightbox`: the root. It holds whether the lightbox is open. Required.
- `LightboxTrigger`: what opens it, usually the image or the file card in the message.
- `LightboxContent`: the full-screen layer, with the scrim behind it. Required.
- `LightboxTitle`: the file name.
- `LightboxClose`: the close button. Required.
- `LightboxStage`: the area that centers the media. Required.
  - `LightboxMedia`: the image or video, at most 80% of the window's height and 86% of its width.
  - `LightboxNavigationButton`: previous (`direction="previous"`) and next (`direction="next"`), 44px buttons 32px from the left and right edges.
  - `LightboxInfoDock` with `LightboxInfoBar`: a small floating bar near the bottom, on `layer-popover` with `shadow-float`, holding `LightboxInfoTitle` (the file name) and `LightboxInfoActions` (such as Download). It is a floating toolbar, 36px tall like the message hover toolbar: its buttons are 28px, 4px inside it and 4px apart, with `radius-chip`, and a 44px tall hit area on a touch screen that reaches only half the gap toward a neighbour ([Shape](https://raft-design.botiverse.build/foundations/shape.md#inside-a-frame)).
  - `LightboxThumbnailStrip` with `LightboxThumbnail`s: 48px thumbnails along the bottom; `active` marks the one shown.
- `LightboxToolbar`: the attachment shell's top bar instead of the floating close button, on `layer-canvas` with a `line-seam` under it. It holds `LightboxHeaderMeta` (a `LightboxHeaderLabel` or [Badge](https://raft-design.botiverse.build/components/badge.md) naming the preview type, then `LightboxTitle`) and `LightboxActions`, with `LightboxClose` last.
- `LightboxHeader`: a plain top row for a title and actions, for shells that need neither toolbar nor floating close.

## Variants

| Variant                                                    | Use for                                                                                                                                         |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Image shell: `LightboxClose` directly in `LightboxContent` | Images and video. The close button floats at the top right; the image has the whole window                                                      |
| Attachment shell: `LightboxToolbar` above `LightboxStage`  | A file preview that fills the stage, such as an HTML page in an `iframe`. The toolbar names the file and holds its actions and the close button |

```tsx
import { Download, X } from "lucide-react";
import {
  Badge,
  Button,
  Lightbox,
  LightboxActions,
  LightboxClose,
  LightboxContent,
  LightboxHeaderMeta,
  LightboxStage,
  LightboxTitle,
  LightboxToolbar,
  LightboxTrigger,
} from "@botiverse/raft-ui";

function AttachmentPreviewTypeLabel() {
  return <Badge variant="primary">HTML preview</Badge>;
}

export function AttachmentPreviewShellComposition() {
  return (
    <Lightbox modal={false}>
      <LightboxTrigger render={<Button type="button" variant="default" />}>
        Open attachment preview
      </LightboxTrigger>
      <LightboxContent render={<section aria-label="Attachment preview" />}>
        <LightboxToolbar>
          <LightboxHeaderMeta title="diagram-export.html · HTML preview">
            <AttachmentPreviewTypeLabel />
            <LightboxTitle>diagram-export.html</LightboxTitle>
          </LightboxHeaderMeta>
          <LightboxActions>
            <Button type="button" variant="default" size="icon-md" aria-label="Download">
              <Download />
            </Button>
            <LightboxClose
              aria-label="Close"
              render={<Button type="button" variant="default" size="icon-md" />}
            >
              <X />
            </LightboxClose>
          </LightboxActions>
        </LightboxToolbar>
        <LightboxStage>
          <iframe
            title="Attachment preview"
            src="https://example.com"
            className="size-full border-0 bg-white"
            referrerPolicy="no-referrer"
            sandbox=""
          />
        </LightboxStage>
      </LightboxContent>
    </Lightbox>
  );
}
```

## States

- **First and last item:** previous or next is disabled. It stays focusable (`aria-disabled`) and ignores clicks.
- **Thumbnails:** the shown item has a solid edge; the others are dimmed until hovered.

## Behavior

- A lightbox appears and disappears without animation, like the other centered overlays ([Motion](https://raft-design.botiverse.build/foundations/motion.md#usage)).
- Esc, the close button, a click on the scrim and Cmd+W (Ctrl+W) close it. Cmd+W closes the lightbox, not the browser tab; pass `closeOnModW={false}` to leave the shortcut to the browser.
- The close button and the bars keep clear of the phone's safe areas.
- The caller keeps which item is shown. Previous, next, the thumbnails and the arrow keys all change that index.

## Content

- Title the lightbox with the file name. A long name truncates at the end.
- Name the preview type in the toolbar's label in sentence case: HTML preview.

## Accessibility

- Lightbox wraps the Base UI Dialog. It provides the `dialog` role, the focus trap and Esc; raft-ui adds the Cmd+W shortcut.
- `LightboxTitle` is plain text, not the dialog's name. Name the dialog yourself: `aria-label` on `LightboxContent`, or a `render` element with one.
- `LightboxContent` doesn't move focus when it opens or return it when it closes (`initialFocus` and `finalFocus` are `false`). Pass them when keyboard users open the lightbox, so focus lands in it and goes back to the trigger.
- Give `LightboxClose`, each `LightboxNavigationButton` and each icon-only action an `aria-label`: "Close", "Previous image", "Next image", "Download".
- Bind the Left and Right arrow keys to previous and next while the lightbox is open.
- Give each `LightboxThumbnail` an `aria-label` ("Show product-shot.png"). `active` changes only how it looks: mark the shown one with `aria-current="true"`.
- Give the image an `alt` that describes it, and an `iframe` a `title`.

## Rules

- Don't build a media viewer from a Dialog, and don't put anything other than media or a file preview in a Lightbox.
- Give the lightbox one close button, at the top right ([Navigation](https://raft-design.botiverse.build/patterns/navigation.md#closing)).
- In the toolbar, a framed button ends its shape on the 16px margin; a frameless icon button, as the last action or the close, ends its icon box there and its hit area hangs past it ([Alignment](https://raft-design.botiverse.build/foundations/alignment.md#the-ends-of-a-header)).
- In Raft Chat every file opens in the attachment shell, images included, so each file type has the same toolbar ([File viewer](https://raft-design.botiverse.build/components.md#file-viewer)).
- Don't add colors or lines beyond the scrim, the info bar and the toolbar ([Built from existing parts](https://raft-design.botiverse.build/components.md#built-from-existing-parts)).

## API

### Lightbox

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `closeOnModW` | `boolean` | `true` |  |
| `defaultOpen` | `boolean` | `false` | Whether the dialog is initially open. |
| `defaultTriggerId` | `string` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. |
| `disablePointerDismissal` | `boolean` | `false` | Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. |
| `modal` | `boolean \| "trap-focus"` | `true` | Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. |
| `onOpenChange` | `(open: boolean, eventDetails: Dialog.Root.ChangeEventDetails) => void` |  | Event handler called when the dialog is opened or closed. |
| `onOpenChangeComplete` | `(open: boolean) => void` |  | Event handler called after any animations complete when the dialog is opened or closed. |
| `open` | `boolean` |  | Whether the dialog is currently open. |
| `triggerId` | `string` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). |

### LightboxActions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxClose

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` | `false` | Determines the element to focus when the dialog is closed. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| HTMLElement \| null \| void)` | `false` | 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. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxHeaderLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxHeaderMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxInfoActions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxInfoBar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxInfoDock

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxInfoTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxMedia

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxNavigationButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `direction` (required) | `"next" \| "previous"` |  |  |
| `disabled` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxPortal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null>` |  | A parent element to render the portal element into. |
| `keepMounted` | `boolean` | `false` | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxStage

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxThumbnail

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxThumbnailStrip

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxToolbar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### LightboxTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `handle` | `Dialog.Handle<unknown>` |  | A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. |
| `id` | `string` |  | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `payload` | `{}` |  | A payload to pass to the dialog when it is opened. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.1.0 (Added): Extended `Lightbox` with thumbnail strip and related chrome parts; added `ComboboxTriggerCount` and `ContextMenuPopup`.
