{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "title": "Dialog",
  "description": "An animated dialog for React and shadcn, with alert dialogs and nested dialogs, built on Base UI.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json",
    "https://eased.dev/r/eased-overlay-core.json",
    "https://eased.dev/r/button.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/dialog/dialog.tsx",
      "content": "\"use client\";\n\nimport { AlertDialog as BaseAlertDialog } from \"@base-ui/react/alert-dialog\";\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { useRef, type ComponentProps, type ReactNode } from \"react\";\nimport { backdropClass, bump, modalMotion, modalSurface, modalViewportClass } from \"../_primitives/overlay-core/overlay-core\";\nimport { Button, type ButtonEdge, type ButtonSize, type ButtonVariant } from \"../button/button\";\n\n/**\n * A modal dialog on Base UI's Dialog, and an alert-dialog variant on Base UI's AlertDialog.\n * It rises 8px at 0.96 on the smooth spring over a blurred scrim, and leaves 4px in 160ms.\n * A dialog opened from inside another steps the parent back and dims it. The alert variant\n * refuses an outside click with a 1.5% bump instead of closing.\n */\n\n/** Groups a dialog's parts and owns its open state (`open` / `defaultOpen` / `onOpenChange`). Modal by default. */\nexport const Dialog = BaseDialog.Root;\n\n/** Closes the dialog. `<DialogClose render={<Button />}>Cancel</DialogClose>`. */\nexport const DialogClose = BaseDialog.Close;\n\n/** Groups an alert dialog's parts. It cannot be dismissed by an outside click, only by its actions or Esc. */\nexport const AlertDialog = BaseAlertDialog.Root;\n\n/** Closes the alert dialog. */\nexport const AlertDialogClose = BaseAlertDialog.Close;\n\nexport type DialogTriggerProps = Omit<ComponentProps<typeof BaseDialog.Trigger>, \"className\"> & {\n  /** The Eased `Button` variant. Default \"secondary\". */\n  variant?: ButtonVariant;\n  /** The Eased `Button` size. Default \"sm\". */\n  size?: ButtonSize;\n  /** Pull a ghost trigger out by its own inset so its label lands on the content line (R104). */\n  edge?: ButtonEdge;\n  className?: string;\n};\n\n/**\n * The button that opens the dialog: an Eased `Button`, which never scales as a popup trigger\n * (R64). Focus returns here on close. Pass `render` to bring your own element.\n */\nexport function DialogTrigger({ variant = \"secondary\", size = \"sm\", edge, className = \"\", render, ...props }: DialogTriggerProps) {\n  return <BaseDialog.Trigger {...props} render={render ?? <Button variant={variant} size={size} edge={edge} className={className} />} />;\n}\n\nexport type AlertDialogTriggerProps = Omit<ComponentProps<typeof BaseAlertDialog.Trigger>, \"className\"> &\n  Pick<DialogTriggerProps, \"variant\" | \"size\" | \"edge\" | \"className\">;\n\n/** The button that opens an alert dialog. Same as `DialogTrigger`; default variant \"danger\". */\nexport function AlertDialogTrigger({ variant = \"danger\", size = \"sm\", edge, className = \"\", render, ...props }: AlertDialogTriggerProps) {\n  return <BaseAlertDialog.Trigger {...props} render={render ?? <Button variant={variant} size={size} edge={edge} className={className} />} />;\n}\n\nconst WIDTHS = { sm: \"w-[24rem]\", md: \"w-[28rem]\", lg: \"w-[36rem]\" } as const;\nexport type DialogSize = keyof typeof WIDTHS;\n\ntype ContentProps<P extends typeof BaseDialog | typeof BaseAlertDialog> = {\n  /** Width: sm 384px, md 448px (default), lg 576px. Never wider than the screen less 32px. */\n  size?: DialogSize;\n  /** Where focus goes on open. Default: the first tabbable element (the popup itself on touch). */\n  initialFocus?: ComponentProps<P[\"Popup\"]>[\"initialFocus\"];\n  /** Where focus returns on close. Default: the trigger. */\n  finalFocus?: ComponentProps<P[\"Popup\"]>[\"finalFocus\"];\n  /** Portal target. Default `document.body`. */\n  container?: HTMLElement | null;\n  /** Skip the enter animation, e.g. when a keyboard shortcut opened it (rule 84). */\n  instant?: boolean;\n  /** Appended to the popup's classes. */\n  className?: string;\n  children?: ReactNode;\n};\n\nexport type DialogContentProps = ContentProps<typeof BaseDialog> & {\n  /** Show the close button in the top corner. Default true. */\n  closeButton?: boolean;\n  /** The close button's accessible name. Default \"Close\". */\n  closeLabel?: string;\n};\n\n/**\n * The dialog: backdrop, viewport and popup. A raised 24px-corner surface with 24px padding\n * (20px under 640px); `--pad` and `--radius-outer` are exposed for concentric children. The\n * close button's icon sits on the content's right edge (R104).\n */\nexport function DialogContent({\n  size = \"md\",\n  initialFocus,\n  finalFocus,\n  container,\n  instant,\n  closeButton = true,\n  closeLabel = \"Close\",\n  className = \"\",\n  children,\n}: DialogContentProps) {\n  return (\n    <BaseDialog.Portal container={container}>\n      <BaseDialog.Backdrop className={backdropClass} />\n      <BaseDialog.Viewport className={modalViewportClass}>\n        <BaseDialog.Popup\n          initialFocus={initialFocus}\n          finalFocus={finalFocus}\n          data-instant={instant || undefined}\n          className={`${modalSurface} ${modalMotion()} ${WIDTHS[size]} max-w-full p-(--pad) ${className}`}\n        >\n          {children}\n          {closeButton && (\n            // A positioned wrapper: Button is `relative` itself (for its spinner), so it can't be the absolute layer.\n            <span className=\"absolute end-[calc(var(--pad)-8px)] top-[calc(var(--pad)-4px)] flex\">\n              <BaseDialog.Close render={<Button variant=\"ghost\" size=\"icon-sm\" hitArea={false} aria-label={closeLabel} />}>\n                <CloseIcon />\n              </BaseDialog.Close>\n            </span>\n          )}\n        </BaseDialog.Popup>\n      </BaseDialog.Viewport>\n    </BaseDialog.Portal>\n  );\n}\n\nexport type AlertDialogContentProps = ContentProps<typeof BaseAlertDialog>;\n\n/**\n * The alert dialog: the same surface, 384px by default, with no close button: the choice is\n * the footer's. A click outside bumps it 1.5% instead of closing it.\n */\nexport function AlertDialogContent({ size = \"sm\", initialFocus, finalFocus, container, instant, className = \"\", children }: AlertDialogContentProps) {\n  const popup = useRef<HTMLDivElement>(null);\n  return (\n    <BaseAlertDialog.Portal container={container}>\n      <BaseAlertDialog.Backdrop className={backdropClass} />\n      <BaseAlertDialog.Viewport\n        className={modalViewportClass}\n        onPointerDown={(e) => {\n          if (e.target !== e.currentTarget) return;\n          bump(popup.current);\n          // Keep focus inside the dialog: the outside click would otherwise drop it to <body>.\n          if (!popup.current?.contains(document.activeElement)) popup.current?.focus();\n        }}\n        onMouseDown={(e) => {\n          if (e.target === e.currentTarget) e.preventDefault();\n        }}\n      >\n        <BaseAlertDialog.Popup\n          ref={popup}\n          initialFocus={initialFocus}\n          finalFocus={finalFocus}\n          data-instant={instant || undefined}\n          className={`${modalSurface} ${modalMotion()} ${WIDTHS[size]} max-w-full p-(--pad) ${className}`}\n        >\n          {children}\n        </BaseAlertDialog.Popup>\n      </BaseAlertDialog.Viewport>\n    </BaseAlertDialog.Portal>\n  );\n}\n\ntype TextProps = { className?: string; children: ReactNode };\n\nconst TITLE = \"pe-8 text-ui-md font-medium text-balance text-text\";\nconst DESCRIPTION = \"mt-1.5 text-ui text-pretty text-text-secondary\";\n\n/** The heading; labels the dialog. Leaves room for the close button. */\nexport function DialogTitle({ className = \"\", children }: TextProps) {\n  return <BaseDialog.Title className={`${TITLE} ${className}`}>{children}</BaseDialog.Title>;\n}\n\n/** Supporting text under the title; describes the dialog. */\nexport function DialogDescription({ className = \"\", children }: TextProps) {\n  return <BaseDialog.Description className={`${DESCRIPTION} ${className}`}>{children}</BaseDialog.Description>;\n}\n\n/** The alert dialog's heading. */\nexport function AlertDialogTitle({ className = \"\", children }: TextProps) {\n  return <BaseAlertDialog.Title className={`${TITLE} pe-0 ${className}`}>{children}</BaseAlertDialog.Title>;\n}\n\n/** What will happen, in a sentence. */\nexport function AlertDialogDescription({ className = \"\", children }: TextProps) {\n  return <BaseAlertDialog.Description className={`${DESCRIPTION} ${className}`}>{children}</BaseAlertDialog.Description>;\n}\n\n/** The dialog's content between the header and the footer: 20px below the description. */\nexport function DialogBody({ className = \"\", children }: TextProps) {\n  return <div className={`mt-5 flex flex-col gap-4 ${className}`}>{children}</div>;\n}\n\n/**\n * The actions, right-aligned on the content's edge (R105), 24px below the body. Under 640px\n * they stack full width with the primary action on top. Put the primary action last.\n */\nexport function DialogFooter({ className = \"\", children }: TextProps) {\n  return (\n    <div className={`mt-6 flex items-center justify-end gap-2 max-sm:flex-col-reverse max-sm:items-stretch ${className}`}>{children}</div>\n  );\n}\n\nfunction CloseIcon() {\n  return (\n    <svg viewBox=\"0 0 16 16\" className=\"size-4 shrink-0\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} strokeLinecap=\"round\" aria-hidden>\n      <path d=\"m4.5 4.5 7 7m0-7-7 7\" />\n    </svg>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/eased/dialog/dialog.tsx"
    }
  ],
  "meta": {
    "details": "A modal dialog and alert dialog on Base UI that rise 8px at 0.96 on a spring over a blurred scrim and leave 4px in 160ms. Nested dialogs step their parent back; alert dialogs answer an outside click with a 1.5% bump.",
    "industries": [
      "saas",
      "devtools",
      "finance",
      "legal",
      "hr"
    ],
    "releaseAt": "2026-10-21"
  },
  "type": "registry:ui"
}