Skip to content
SpatialGenerated from the public API

MorphingDialog

A dialog that grows out of the element that opened it, keeping the focus contract.

@luc/ui/motion

Live preview

A centred panel that grows out of the card it was opened from; Radix still owns focus, Escape and the unmount.

<MorphingDialog>
  <MorphingDialogTrigger>
    <Card className="w-56 p-4">Run detail</Card>
  </MorphingDialogTrigger>
  {/* No className: it lands on the fixed centring frame, not the panel.
      Size the panel with `width` or `panelClassName`. */}
  <MorphingDialogContent width="md">
    <div className="flex flex-col gap-4 p-5">
      <MorphingDialogTitle>Run detail</MorphingDialogTitle>
      <MorphingDialogDescription>The panel grows out of the card.</MorphingDialogDescription>
      <MorphingDialogClose asChild>
        <IconButton label="Close" variant="ghost" size="sm" />
      </MorphingDialogClose>
    </div>
  </MorphingDialogContent>
</MorphingDialog>

Other configurations

The same primitive, arranged differently. Each snippet is printed from the props its preview above was rendered with.

A narrow panel, one fact

A narrow panel with one fact: the panel is content-sized, and the trigger keeps its own width.

<MorphingDialog>
  <MorphingDialogTrigger>
    <Card className="w-56 p-4">Sharpe</Card>
  </MorphingDialogTrigger>
  {/* The panel is sized by `width`; the trigger's own box is unchanged, so
      the morph still reads as the same object growing. */}
  <MorphingDialogContent width="sm">
    <div className="flex flex-col gap-4 p-5">
      <MorphingDialogTitle>Sharpe</MorphingDialogTitle>
      <dl className="grid grid-cols-1 gap-3">
        <div>
          <dt>Sharpe</dt>
          <dd>1.42</dd>
        </div>
      </dl>
    </div>
  </MorphingDialogContent>
</MorphingDialog>

Usage

import { MorphingDialog } from "@luc/ui/motion";
<MorphingDialog>
  <MorphingDialogTrigger>
    <Card className="w-56 p-4">Run detail</Card>
  </MorphingDialogTrigger>
  {/* No className: it lands on the fixed centring frame, not the panel.
      Size the panel with `width` or `panelClassName`. */}
  <MorphingDialogContent width="md">
    <div className="flex flex-col gap-4 p-5">
      <MorphingDialogTitle>Run detail</MorphingDialogTitle>
      <MorphingDialogDescription>The panel grows out of the card.</MorphingDialogDescription>
      <MorphingDialogClose asChild>
        <IconButton label="Close" variant="ghost" size="sm" />
      </MorphingDialogClose>
    </div>
  </MorphingDialogContent>
</MorphingDialog>

API

Exported symbols
ExportKindSignature
MorphingDialog4 documented props({ children, open, defaultOpen, onOpenChange }: MorphingDialogProps) => import("@types/react").JSX.Element
MorphingDialogClose1 documented propsimport("@types/react").ForwardRefExoticComponent<RadixDialog.DialogCloseProps & import("@types/react").RefAttributes<HTMLButtonElement>>
MorphingDialogContent15 documented props({ className, panelClassName, frameStyle, panelStyle, panelSlot, width, children, ...props }: MorphingDialogContentProps) => import("@types/react").JSX.Element
MorphingDialogDescription1 documented propsimport("@types/react").ForwardRefExoticComponent<RadixDialog.DialogDescriptionProps & import("@types/react").RefAttributes<HTMLParagraphElement>>
MorphingDialogTitle1 documented propsimport("@types/react").ForwardRefExoticComponent<RadixDialog.DialogTitleProps & import("@types/react").RefAttributes<HTMLHeadingElement>>
MorphingDialogTriggerexport({ className, children, ...props }: MorphingDialogTriggerProps) => import("@types/react").JSX.Element

MorphingDialog props

Type: MorphingDialogProps

Values the compiler reads from the declaration.

MorphingDialog props
PropTypeDefaultNotes
childrenReactNode—
defaultOpen?boolean | undefined—
onOpenChange?((open: boolean) => void) | undefined—
open?boolean | undefined—

MorphingDialogClose props

Type: DialogCloseProps & RefAttributes<HTMLButtonElement>

Values the compiler reads from the declaration.

MorphingDialogClose props
PropTypeDefaultNotes
asChild?inheritedboolean | undefined—

MorphingDialogContent props

Type: MorphingDialogContentProps

Values the compiler reads from the declaration.

MorphingDialogContent props
PropTypeDefaultNotes
asChild?inheritedboolean | undefined—
childrenReactNode—
deferPointerDownOutside?inheritedboolean | undefined—When `true`, a `'pointerdown'` event outside of the layered element will wait for the interaction's click event before dispatching, allowing third-party code to stop propagation o…
forceMount?inheritedtrue | undefined—Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
frameStyle?CSSProperties | undefined—Layout for the centring wrapper, when the framing depends on a measurement rather than a breakpoint (a panel that fills a shell's workspace has to set its own insets). Plain CSS p…
onCloseAutoFocus?inherited((event: Event) => void) | undefined—Event handler called when auto-focusing on close. Can be prevented.
onEscapeKeyDown?inherited((event: KeyboardEvent) => void) | undefined—Event handler called when the escape key is down. Can be prevented.
onFocusOutside?inherited((event: FocusOutsideEvent) => void) | undefined—Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented.
onInteractOutside?inherited((event: PointerDownOutsideEvent | FocusOutsideEvent) => void) | undefined—Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown` event happens outside or focus moves outside of it. Can be prev…
onOpenAutoFocus?inherited((event: Event) => void) | undefined—Event handler called when auto-focusing on open. Can be prevented.
onPointerDownOutside?inherited((event: PointerDownOutsideEvent) => void) | undefined—Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented.
panelClassName?string | undefined—Extra classes for the panel itself (not the centring wrapper).
panelSlot?string | undefined—`data-slot` for the panel box itself. The wrapper keeps `morphing-dialog`, so a caller can name the box it actually sizes (`h-…`/`w-…` land on the panel, not on the full-viewport …
panelStyle?CSSProperties | undefined—Layout for the panel box itself. A resizable panel owns its own width/height as inline style (a `dvh`-based class cannot express "whatever the reader dragged to"), and the style h…
width?"none" | "sm" | "md" | "lg" | "xl" | undefined—Panel width preset. `none` lets the panel (or its own classes) decide.

MorphingDialogDescription props

Type: DialogDescriptionProps & RefAttributes<HTMLParagraphElement>

Values the compiler reads from the declaration.

MorphingDialogDescription props
PropTypeDefaultNotes
asChild?inheritedboolean | undefined—

MorphingDialogTitle props

Type: DialogTitleProps & RefAttributes<HTMLHeadingElement>

Values the compiler reads from the declaration.

MorphingDialogTitle props
PropTypeDefaultNotes
asChild?inheritedboolean | undefined—

Accessibility

The accessibility contract is the one the primitive underneath the component already honours — the platform element for a control, or the ARIA pattern the component implements — and the Showcase's own pages are scanned for violations with axe.

What happens on the server, and when hydration changes it.

Family

Spatial

Depth, layering and geometry — motion that reads as physical space.

Luc UI v1.0MorphingDialogdark theme