lensclear design system

Modal surfaces on base-ui. A blurred backdrop, a centered popup, a close button in the corner.

Overview

Compose the parts. Dialog holds state, DialogTrigger opens it, DialogContent renders the backdrop and the popup into a portal.

DialogHeader stacks the title and description, centered on phones and left-aligned from sm up. DialogFooter reverses its column on phones so the primary action sits on top, then rows the actions to the right from sm up.

The trigger and close parts render a native button with no face of its own. Put a Box inside for the visual, or pass render to swap the element.

Basic

Uncontrolled. The trigger opens, any DialogClose or the corner button closes, and Escape or a click on the backdrop closes too.

<Dialog>
  <DialogTrigger>Open dialog</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Withdraw collateral</DialogTitle>
      <DialogDescription>The position stays healthy after this.</DialogDescription>
    </DialogHeader>
    <Text variant="body">1.25 SOL leaves the vault.</Text>
    <DialogFooter>
      <DialogClose>Cancel</DialogClose>
      <DialogClose>Withdraw</DialogClose>
    </DialogFooter>
  </DialogContent>
</Dialog>

Controlled

Own the open state when something outside the dialog needs to close it, like a confirm handler.

Nothing confirmed yet

const [open, setOpen] = useState(false);

<Dialog open={open} onOpenChange={setOpen}>
  <DialogTrigger>Confirm something</DialogTrigger>
  <DialogContent>
    <DialogTitle>Sure?</DialogTitle>
    <DialogFooter>
      <DialogClose>Not now</DialogClose>
      <button type="button" onClick={() => setOpen(false)}>Confirm</button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Backdrop content

overlayChildren renders inside the backdrop, behind the popup. overlayClassName and overlayStyle reach the same element.

<DialogContent
  overlayChildren={<Text color="inverse">Rendered inside the backdrop</Text>}
>
  ...
</DialogContent>

Dialog props

The root is base-ui's Dialog.Root. The common props are listed.

openboolean

Controlled open state. Pair with onOpenChange.

defaultOpenbooleandefault: false

Open on first render, uncontrolled.

onOpenChange((open: boolean, eventDetails: DialogPrimitive.Root.ChangeEventDetails) => void)

Fires on open and close, with the reason.

modalboolean | "trap-focus"default: true

Whether the page behind is inert. trap-focus keeps focus in without blocking pointer events outside.

DialogContent props

childrenReactNode

The popup body. The close button is appended after it.

overlayChildrenReactNode

Rendered inside the backdrop, behind the popup.

overlayClassNamestring

Bridge className for the backdrop. Tailwind product code only.

overlayStyleCSSProperties

Inline style for the backdrop.

classNamestring

Bridge className for the popup. Tailwind product code only, it dies with the bridge.

Inline style for the popup, merged after the lens styles.

initialFocusboolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void)

Where focus lands on open. Default is the first tabbable element.

finalFocusboolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void)

Where focus returns on close. Default is the trigger.

DialogTitle props

childrenReactNode

The heading. Renders an h2 wired to the dialog's aria-labelledby.

classNamestring

Bridge className, appended after the lens styles.

DialogDescription props

childrenReactNode

One line under the title. Renders a p wired to aria-describedby.

classNamestring

Bridge className, appended after the lens styles.