{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "type": "registry:ui",
  "title": "Dialog",
  "description": "Native <dialog> + 30-line script. Focus trap, ESC, accessible modal — all from the platform. Backdrop click to close, X button, htmx-rendered variants.",
  "files": [
    {
      "path": "registry/ui/dialog.tsx",
      "type": "registry:ui",
      "target": "components/ui/dialog.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport type { Child, PropsWithChildren } from \"hono/jsx\"\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Dialog — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// We use the native HTML <dialog> element + .showModal() so the platform\n// gives us:\n//   - Focus trap                       (no JS focus management required)\n//   - ESC to close                     (browser default)\n//   - aria-modal / role=dialog         (set by showModal())\n//   - Focus restoration to the opener  (browser default)\n//   - ::backdrop pseudo-element        (we colour it via CSS in input.css)\n//\n// We add on top:\n//   - shadcn box styles (rounded border, shadow, centered).\n//   - DialogTrigger / DialogClose data attributes wired up in public/site.js.\n//   - Click-on-backdrop closes (also in site.js).\n//\n// Composition mirrors shadcn's React API:\n//   <Dialog id=\"my-dialog\">\n//     <DialogHeader>\n//       <DialogTitle>...</DialogTitle>\n//       <DialogDescription>...</DialogDescription>\n//     </DialogHeader>\n//     <DialogBody>...form fields...</DialogBody>\n//     <DialogFooter>\n//       <DialogClose><Button variant=\"outline\">Cancel</Button></DialogClose>\n//       <Button>Save</Button>\n//     </DialogFooter>\n//   </Dialog>\n//\n//   <DialogTrigger dialogFor=\"my-dialog\">Open</DialogTrigger>\n\nconst dialogBase =\n  \"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg \" +\n  \"grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none \" +\n  // Native <dialog> sits at the top layer; hide it when not open so layout\n  // doesn't shift.\n  \"hidden open:grid \" +\n  // ::backdrop styling.\n  \"backdrop:bg-black/60 backdrop:backdrop-blur-sm\"\n\nexport function dialogClasses(opts?: { class?: ClassValue }): string {\n  return cn(dialogBase, opts?.class)\n}\n\ntype DialogProps = PropsWithChildren<{\n  id: string\n  // Set to false to disable the click-on-backdrop-closes behaviour. The\n  // browser-native `closedby` attribute (below) is a stronger signal — if\n  // you set it to \"any\" the browser handles backdrop dismissal natively.\n  closeOnBackdrop?: boolean\n  // Pre-open the dialog on initial render (useful for htmx swaps that return\n  // an already-open dialog).\n  open?: boolean\n  // Native `closedby` attribute (HTML Living Standard / WHATWG). Controls\n  // how the user can dismiss the dialog:\n  //   - \"any\"          — ESC, light dismiss (backdrop click), and code\n  //   - \"closerequest\" — ESC and code only  (default for showModal())\n  //   - \"none\"         — only code can close (e.g. terms acceptance)\n  // See repos/mdn/files/en-us/web/html/reference/elements/dialog/index.md:19-35\n  closedby?: \"any\" | \"closerequest\" | \"none\"\n  // role variant. \"alertdialog\" demands a synchronous user response and is\n  // announced by assistive tech with higher urgency. APG requires it to\n  // carry a description (aria-describedby).\n  // See repos/aria-practices/content/patterns/alertdialog/.\n  role?: \"dialog\" | \"alertdialog\"\n  // Render the X close button in the top-right corner (default true).\n  showCloseButton?: boolean\n  class?: ClassValue\n  ariaLabelledby?: string\n  ariaDescribedby?: string\n}>\n\nexport function Dialog(props: DialogProps) {\n  const {\n    id,\n    children,\n    closeOnBackdrop = true,\n    open,\n    closedby,\n    role = \"dialog\",\n    showCloseButton = true,\n    class: className,\n    ariaLabelledby,\n    ariaDescribedby,\n  } = props\n  return (\n    <dialog\n      id={id}\n      open={open}\n      class={dialogClasses({ class: className })}\n      data-slot=\"dialog\"\n      data-close-on-backdrop={closeOnBackdrop ? \"true\" : undefined}\n      // Native closedby attribute (only emitted when set so we don't override\n      // the browser's default of \"closerequest\" for showModal()).\n      {...(closedby ? { closedby } : {})}\n      // role override — Hono JSX renders the dialog with implicit role=\"dialog\";\n      // we set it explicitly so consumers can switch to alertdialog.\n      role={role}\n      aria-labelledby={ariaLabelledby ?? `${id}-title`}\n      aria-describedby={ariaDescribedby ?? `${id}-description`}\n    >\n      {children}\n      {showCloseButton && (\n        <button\n          type=\"button\"\n          data-dialog-close=\"true\"\n          aria-label=\"Close\"\n          class=\"absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\"\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"size-4\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"M18 6 6 18\" />\n            <path d=\"m6 6 12 12\" />\n          </svg>\n        </button>\n      )}\n    </dialog>\n  )\n}\n\nexport function DialogHeader(props: PropsWithChildren<{ class?: ClassValue }>) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      class={cn(\"flex flex-col gap-1.5 text-left\", props.class)}\n    >\n      {props.children}\n    </div>\n  )\n}\n\nexport function DialogTitle(\n  props: PropsWithChildren<{ id?: string; class?: ClassValue }>,\n) {\n  return (\n    <h2\n      id={props.id}\n      data-slot=\"dialog-title\"\n      class={cn(\"text-lg leading-none font-semibold\", props.class)}\n    >\n      {props.children}\n    </h2>\n  )\n}\n\nexport function DialogDescription(\n  props: PropsWithChildren<{ id?: string; class?: ClassValue }>,\n) {\n  return (\n    <p\n      id={props.id}\n      data-slot=\"dialog-description\"\n      class={cn(\"text-sm text-muted-foreground\", props.class)}\n    >\n      {props.children}\n    </p>\n  )\n}\n\nexport function DialogBody(props: PropsWithChildren<{ class?: ClassValue }>) {\n  return (\n    <div\n      data-slot=\"dialog-body\"\n      class={cn(\"text-sm text-foreground\", props.class)}\n    >\n      {props.children}\n    </div>\n  )\n}\n\nexport function DialogFooter(props: PropsWithChildren<{ class?: ClassValue }>) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      class={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        props.class,\n      )}\n    >\n      {props.children}\n    </div>\n  )\n}\n\n// Close button wrapper — clones any single child (a Button works), attaches\n// data-dialog-close so site.js can intercept the click and call .close() on\n// the nearest <dialog> ancestor.\ntype DialogCloseProps = PropsWithChildren<{\n  // If true (default), the wrapped child gets data-dialog-close attached.\n  // Set false to attach to a non-child (e.g. when you render your own button\n  // here and add data-dialog-close yourself).\n  attachToChild?: boolean\n  // Native Invoker Commands mode (opt-in). When set, render a real <button>\n  // that closes the dialog with zero JS — the platform equivalent of\n  // .close()/.requestClose():\n  //   - \"close\"         → declarative HTMLDialogElement.close()\n  //   - \"request-close\" → fires a cancelable `cancel` event first, so an\n  //                       unsaved-changes guard can preventDefault() it.\n  // The data-dialog-close + site.js path remains the default fallback.\n  // See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74\n  //     repos/mdn/files/en-us/web/html/reference/elements/dialog/index.md:55-71\n  command?: \"close\" | \"request-close\"\n  // Target dialog id for the invoker button. Defaults to the closest <dialog>\n  // ancestor (browsers resolve commandfor up the tree), but pass it when the\n  // button lives outside the dialog.\n  commandfor?: string\n  // Button `value` — with the close/request-close commands the platform copies\n  // this into HTMLDialogElement.returnValue, so the `close` event can tell\n  // which control closed the dialog (e.g. \"confirm\" vs \"cancel\").\n  // See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:149-152\n  value?: string\n  class?: ClassValue\n}>\nexport function DialogClose(props: DialogCloseProps) {\n  const { children, attachToChild = true, command, commandfor, value, class: className } = props\n  // Native invoker mode: render a real <button> with command/commandfor so the\n  // browser closes the dialog (and copies `value` into returnValue) with no JS.\n  if (command) {\n    return (\n      <button type=\"button\" command={command} commandfor={commandfor} value={value} class={cn(className)}>\n        {children}\n      </button>\n    )\n  }\n  if (!attachToChild) return <>{children}</>\n  // The simpler pattern: render a span with data-dialog-close=\"true\"; the JS\n  // event listener walks up to find the closest <dialog>. This way we don't\n  // need cloneElement and the consumer can pass anything as the child.\n  return (\n    <span data-dialog-close=\"true\" class=\"contents\">\n      {children}\n    </span>\n  )\n}\n\n// Trigger button — clicks open the dialog whose id matches dialogFor.\ntype DialogTriggerProps = PropsWithChildren<{\n  dialogFor: string\n  class?: ClassValue\n  // Render mode: \"wrapper\" (default — wraps the child in a span so the parent\n  // can pass any element like a styled Button) or \"button\" (render a native\n  // <button> with the provided class).\n  render?: \"wrapper\" | \"button\"\n  type?: \"button\" | \"submit\"\n  id?: string\n  ariaHaspopup?: string\n  // Native Invoker Commands mode (opt-in). When true, render a real <button\n  // command=\"show-modal\" commandfor={dialogFor}> that opens the dialog as a\n  // modal with zero JS — the declarative equivalent of .showModal(). The\n  // browser also wires implicit control↔dialog accessibility. The data-* +\n  // site.js path stays the default fallback.\n  // See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74\n  //     repos/mdn/files/en-us/web/html/reference/elements/dialog/index.md:55-71\n  native?: boolean\n}>\nexport function DialogTrigger(props: DialogTriggerProps) {\n  const { dialogFor, render = \"wrapper\", children, class: className, id, type = \"button\", ariaHaspopup = \"dialog\", native } = props\n  if (native) {\n    return (\n      <button\n        id={id}\n        type={type}\n        class={cn(className)}\n        command=\"show-modal\"\n        commandfor={dialogFor}\n        aria-haspopup={ariaHaspopup}\n      >\n        {children}\n      </button>\n    )\n  }\n  if (render === \"button\") {\n    return (\n      <button\n        id={id}\n        type={type}\n        class={cn(className)}\n        data-dialog-trigger=\"true\"\n        data-dialog-target={dialogFor}\n        aria-haspopup={ariaHaspopup}\n      >\n        {children}\n      </button>\n    )\n  }\n  return (\n    <span\n      data-dialog-trigger=\"true\"\n      data-dialog-target={dialogFor}\n      class=\"contents\"\n    >\n      {children}\n    </span>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/dialog.html",
      "type": "registry:file",
      "target": "templates/components/dialog.html",
      "content": "{# Dialog macros — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/dialog.tsx. Uses the native <dialog> element + the\n   wiring in public/site.js (data-dialog-trigger / data-dialog-close).\n\n   Usage:\n     {% from \"components/dialog.html\" import dialog, dialog_trigger %}\n\n     {{ dialog_trigger(\"Open\", dialog_for=\"my-dialog\", class_=\"…btn classes\") }}\n\n     {% call dialog(id=\"my-dialog\", title=\"Are you sure?\",\n                    description=\"This action cannot be undone.\") %}\n       <!-- body content -->\n       <p>The item will be permanently deleted from your library.</p>\n\n       <div class=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n         <button type=\"button\" data-dialog-close=\"true\"\n                 class=\"… button outline classes …\">Cancel</button>\n         <button type=\"button\" class=\"… button default classes …\"\n                 hx-post=\"/items/42/delete\">Delete</button>\n       </div>\n     {% endcall %} #}\n\n{% macro dialog(\n    id,\n    title=none,\n    description=none,\n    close_on_backdrop=true,\n    show_close_button=true,\n    open=false,\n    closedby=none,\n    role=\"dialog\",\n    extra_class=\"\"\n) %}\n{%- set base -%}\nfixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none hidden open:grid backdrop:bg-black/60 backdrop:backdrop-blur-sm\n{%- endset -%}\n<dialog id=\"{{ id }}\"\n        {%- if open %} open{% endif %}\n        {%- if closedby %} closedby=\"{{ closedby }}\"{% endif %}\n        role=\"{{ role }}\"\n        class=\"{{ base }} {{ extra_class }}\"\n        data-slot=\"dialog\"\n        {%- if close_on_backdrop %} data-close-on-backdrop=\"true\"{% endif %}\n        aria-labelledby=\"{{ id }}-title\"\n        aria-describedby=\"{{ id }}-description\">\n  {%- if title or description %}\n  <div data-slot=\"dialog-header\" class=\"flex flex-col gap-1.5 text-left\">\n    {% if title %}<h2 id=\"{{ id }}-title\" data-slot=\"dialog-title\" class=\"text-lg leading-none font-semibold\">{{ title }}</h2>{% endif %}\n    {% if description %}<p id=\"{{ id }}-description\" data-slot=\"dialog-description\" class=\"text-sm text-muted-foreground\">{{ description }}</p>{% endif %}\n  </div>\n  {%- endif %}\n  {{ caller() }}\n  {%- if show_close_button %}\n  <button type=\"button\" data-dialog-close=\"true\" aria-label=\"Close\"\n          class=\"absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\">\n    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\n      <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n    </svg>\n  </button>\n  {%- endif %}\n</dialog>\n{% endmacro %}\n\n{# native=true renders a native Invoker Commands button\n   (<button command=\"show-modal\" commandfor=\"…\">) that opens the dialog as a\n   modal with zero JS — the declarative equivalent of .showModal(). The\n   data-dialog-trigger + site.js path stays the default fallback.\n   See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74 #}\n{% macro dialog_trigger(label, dialog_for, type=\"button\", id=none, class_=\"\", native=false) %}\n<button {% if id %} id=\"{{ id }}\"{% endif %}\n        type=\"{{ type }}\"\n        class=\"{{ class_ }}\"\n        {%- if native %} command=\"show-modal\" commandfor=\"{{ dialog_for }}\"\n        {%- else %} data-dialog-trigger=\"true\" data-dialog-target=\"{{ dialog_for }}\"{% endif %}\n        aria-haspopup=\"dialog\">{{ label }}</button>\n{% endmacro %}\n\n{# Native Invoker Commands close button (opt-in). command is \"close\"\n   (declarative .close()) or \"request-close\" (fires a cancelable `cancel`\n   event first, for unsaved-changes guards). `value` is copied into the\n   dialog's returnValue so the close event can tell which control fired.\n   commandfor defaults to the closest <dialog> ancestor; pass it when the\n   button lives outside the dialog.\n   See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74,149-152 #}\n{% macro dialog_close(label, command=\"close\", commandfor=none, value=none, class_=\"\") %}\n<button type=\"button\"\n        command=\"{{ command }}\"\n        {%- if commandfor %} commandfor=\"{{ commandfor }}\"{% endif %}\n        {%- if value is not none %} value=\"{{ value }}\"{% endif %}\n        class=\"{{ class_ }}\">{{ label }}</button>\n{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/dialog.tmpl",
      "type": "registry:file",
      "target": "components/dialog.tmpl",
      "content": "{{/*\n  Dialog template — shadcn-htmx, htmx v4 + Tailwind v4.\n  Mirrors registry/ui/dialog.tsx.\n\n  This template renders a complete <dialog> element. For body/footer content\n  you compose your own HTML and place it in .Body (rendered via \"safe\" raw\n  HTML — make sure it's trusted!) or call the template with an embedded\n  Define block.\n\n  Usage:\n\n      type DialogArgs struct {\n          ID, Title, Description string\n          Body                   template.HTML // already-rendered HTML\n          CloseOnBackdrop        bool          // default true\n          ShowCloseButton        bool          // default true\n          Open                   bool\n      }\n\n      tpl.ExecuteTemplate(w, \"dialog\", DialogArgs{\n          ID: \"confirm-delete\", Title: \"Delete item?\",\n          Description: \"This cannot be undone.\",\n          Body: template.HTML(`\n              <div class=\"flex justify-end gap-2\">\n                <button type=\"button\" data-dialog-close=\"true\">Cancel</button>\n                <button type=\"button\" hx-post=\"/items/42/delete\">Delete</button>\n              </div>`),\n      })\n\n  Companion: \"dialog_trigger\" template (below) renders the open button.\n*/}}\n\n{{define \"dialog\"}}\n{{- $base := \"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none hidden open:grid backdrop:bg-black/60 backdrop:backdrop-blur-sm\" -}}\n{{- $closeOnBackdrop := true -}}{{- if .CloseOnBackdropSet}}{{$closeOnBackdrop = .CloseOnBackdrop}}{{end -}}\n{{- $showCloseButton := true -}}{{- if .ShowCloseButtonSet}}{{$showCloseButton = .ShowCloseButton}}{{end -}}\n{{- $role := or .Role \"dialog\" -}}\n<dialog id=\"{{.ID}}\"\n        {{- if .Open}} open{{end}}\n        {{- if .ClosedBy}} closedby=\"{{.ClosedBy}}\"{{end}}\n        role=\"{{$role}}\"\n        class=\"{{$base}}\"\n        data-slot=\"dialog\"\n        {{- if $closeOnBackdrop}} data-close-on-backdrop=\"true\"{{end}}\n        aria-labelledby=\"{{.ID}}-title\"\n        aria-describedby=\"{{.ID}}-description\">\n  {{- if or .Title .Description}}\n  <div data-slot=\"dialog-header\" class=\"flex flex-col gap-1.5 text-left\">\n    {{- if .Title}}\n    <h2 id=\"{{.ID}}-title\" data-slot=\"dialog-title\" class=\"text-lg leading-none font-semibold\">{{.Title}}</h2>\n    {{- end}}\n    {{- if .Description}}\n    <p id=\"{{.ID}}-description\" data-slot=\"dialog-description\" class=\"text-sm text-muted-foreground\">{{.Description}}</p>\n    {{- end}}\n  </div>\n  {{- end}}\n  {{.Body}}\n  {{- if $showCloseButton}}\n  <button type=\"button\" data-dialog-close=\"true\" aria-label=\"Close\"\n          class=\"absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\">\n    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\n      <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n    </svg>\n  </button>\n  {{- end}}\n</dialog>\n{{end}}\n\n{{/*\n  dialog_trigger — open button. Set .Native=true to render a native Invoker\n  Commands button (<button command=\"show-modal\" commandfor=\"…\">) that opens\n  the dialog as a modal with zero JS — the declarative equivalent of\n  .showModal(). The data-dialog-trigger + site.js path stays the default.\n  See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74\n*/}}\n{{define \"dialog_trigger\"}}\n<button {{if .ID}}id=\"{{.ID}}\"{{end}}\n        type=\"{{or .Type \"button\"}}\"\n        class=\"{{.Class}}\"\n        {{- if .Native}} command=\"show-modal\" commandfor=\"{{.DialogFor}}\"\n        {{- else}} data-dialog-trigger=\"true\" data-dialog-target=\"{{.DialogFor}}\"{{end}}\n        aria-haspopup=\"dialog\">{{.Label}}</button>\n{{end}}\n\n{{/*\n  dialog_close — native Invoker Commands close button (opt-in). .Command is\n  \"close\" (declarative .close()) or \"request-close\" (fires a cancelable\n  `cancel` event first, for unsaved-changes guards). .Value is copied into the\n  dialog's returnValue so the close event can tell which control fired.\n  .CommandFor defaults to the closest <dialog> ancestor; set it when the\n  button lives outside the dialog.\n  See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74,149-152\n*/}}\n{{define \"dialog_close\"}}\n<button type=\"button\"\n        command=\"{{or .Command \"close\"}}\"\n        {{- if .CommandFor}} commandfor=\"{{.CommandFor}}\"{{end}}\n        {{- if .Value}} value=\"{{.Value}}\"{{end}}\n        class=\"{{.Class}}\">{{.Label}}</button>\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/dialog.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/dialog.ex",
      "content": "defmodule ShadcnHtmx.Components.Dialog do\n  @moduledoc \"\"\"\n  Dialog — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Mirrors registry/ui/dialog.tsx. Renders the native <dialog> element +\n  attaches the data attributes that public/site.js looks for to open / close.\n\n  ## Examples\n\n      <.dialog_trigger dialog_for=\"confirm-delete\" class=\"…btn-classes…\">\n        Delete item\n      </.dialog_trigger>\n\n      <.dialog id=\"confirm-delete\" title=\"Delete item?\"\n               description=\"This action cannot be undone.\">\n        <div class=\"flex justify-end gap-2\">\n          <button type=\"button\" data-dialog-close=\"true\">Cancel</button>\n          <button type=\"button\" hx-post={~p\"/items/\\#{@item.id}\"} hx-method=\"delete\">Delete</button>\n        </div>\n      </.dialog>\n  \"\"\"\n\n  use Phoenix.Component\n\n  @dialog_base \"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg \" <>\n                 \"grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none \" <>\n                 \"hidden open:grid \" <>\n                 \"backdrop:bg-black/60 backdrop:backdrop-blur-sm\"\n\n  attr :id, :string, required: true\n  attr :title, :string, default: nil\n  attr :description, :string, default: nil\n  attr :close_on_backdrop, :boolean, default: true\n  attr :show_close_button, :boolean, default: true\n  attr :open, :boolean, default: false\n  # Native HTML `closedby` attribute (HTML Living Standard).\n  # See repos/mdn/files/en-us/web/html/reference/elements/dialog/index.md:19-35.\n  attr :closedby, :string, default: nil, values: [\"any\", \"closerequest\", \"none\", nil]\n  # APG: role=\"alertdialog\" demands a synchronous response and is announced\n  # with higher urgency by AT. Requires aria-describedby.\n  attr :role, :string, default: \"dialog\", values: ~w(dialog alertdialog)\n  attr :class, :string, default: nil\n\n  slot :inner_block, required: true\n\n  def dialog(assigns) do\n    assigns = assign(assigns, :base, @dialog_base)\n\n    ~H\"\"\"\n    <dialog\n      id={@id}\n      open={@open}\n      closedby={@closedby}\n      role={@role}\n      class={[@base, @class]}\n      data-slot=\"dialog\"\n      data-close-on-backdrop={@close_on_backdrop && \"true\"}\n      aria-labelledby={\"#{@id}-title\"}\n      aria-describedby={\"#{@id}-description\"}\n    >\n      <div :if={@title || @description} data-slot=\"dialog-header\" class=\"flex flex-col gap-1.5 text-left\">\n        <h2 :if={@title} id={\"#{@id}-title\"} data-slot=\"dialog-title\" class=\"text-lg leading-none font-semibold\">{@title}</h2>\n        <p :if={@description} id={\"#{@id}-description\"} data-slot=\"dialog-description\" class=\"text-sm text-muted-foreground\">{@description}</p>\n      </div>\n      {render_slot(@inner_block)}\n      <button\n        :if={@show_close_button}\n        type=\"button\"\n        data-dialog-close=\"true\"\n        aria-label=\"Close\"\n        class=\"absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\"\n      >\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\n          <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n        </svg>\n      </button>\n    </dialog>\n    \"\"\"\n  end\n\n  attr :dialog_for, :string, required: true\n  attr :type, :string, default: \"button\"\n  attr :class, :string, default: nil\n  # native: true renders a native Invoker Commands button\n  # (<button command=\"show-modal\" commandfor=\"…\">) that opens the dialog as a\n  # modal with zero JS — the declarative equivalent of .showModal(). The\n  # data-dialog-trigger + site.js path stays the default fallback.\n  # See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74.\n  attr :native, :boolean, default: false\n  attr :rest, :global\n  slot :inner_block, required: true\n\n  def dialog_trigger(assigns) do\n    ~H\"\"\"\n    <button\n      type={@type}\n      class={@class}\n      command={@native && \"show-modal\"}\n      commandfor={@native && @dialog_for}\n      data-dialog-trigger={!@native && \"true\"}\n      data-dialog-target={!@native && @dialog_for}\n      aria-haspopup=\"dialog\"\n      {@rest}\n    >\n      {render_slot(@inner_block)}\n    </button>\n    \"\"\"\n  end\n\n  # Native Invoker Commands close button (opt-in). `command` is \"close\"\n  # (declarative .close()) or \"request-close\" (fires a cancelable `cancel`\n  # event first, for unsaved-changes guards). `value` is copied into the\n  # dialog's returnValue so the close event can tell which control fired.\n  # `commandfor` defaults to the closest <dialog> ancestor; set it when the\n  # button lives outside the dialog.\n  # See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74,149-152.\n  attr :command, :string, default: \"close\", values: ~w(close request-close)\n  attr :commandfor, :string, default: nil\n  attr :value, :string, default: nil\n  attr :class, :string, default: nil\n  attr :rest, :global\n  slot :inner_block, required: true\n\n  def dialog_close(assigns) do\n    ~H\"\"\"\n    <button\n      type=\"button\"\n      command={@command}\n      commandfor={@commandfor}\n      value={@value}\n      class={@class}\n      {@rest}\n    >\n      {render_slot(@inner_block)}\n    </button>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/dialog.html",
      "type": "registry:file",
      "target": "snippets/dialog.html",
      "content": "<!--\n  shadcn-htmx — raw HTML dialog snippets.\n\n  Uses the native <dialog> element. The wiring (open / close / backdrop click)\n  is JS-driven in shadcn-htmx because <dialog> doesn't have built-in trigger\n  attributes; we lean on a tiny script (see public/site.js) that listens for\n  data-dialog-trigger / data-dialog-close clicks.\n\n  Minimal inline JS to copy alongside this snippet:\n\n    <script>\n      document.addEventListener('click', (e) => {\n        const t = e.target.closest('[data-dialog-trigger]')\n        if (t) document.getElementById(t.dataset.dialogTarget)?.showModal()\n        const c = e.target.closest('[data-dialog-close]')\n        if (c) c.closest('dialog')?.close()\n      })\n      document.querySelectorAll('dialog[data-close-on-backdrop=\"true\"]').forEach((d) => {\n        d.addEventListener('click', (e) => { if (e.target === d) d.close() })\n      })\n    </script>\n-->\n\n<!-- Trigger button -->\n<button type=\"button\"\n        data-dialog-trigger=\"true\"\n        data-dialog-target=\"confirm-delete\"\n        aria-haspopup=\"dialog\"\n        class=\"inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 text-sm font-medium text-white hover:bg-destructive/90\">\n  Delete item\n</button>\n\n<!-- The dialog itself -->\n<dialog id=\"confirm-delete\"\n        data-slot=\"dialog\"\n        data-close-on-backdrop=\"true\"\n        aria-labelledby=\"confirm-delete-title\"\n        aria-describedby=\"confirm-delete-description\"\n        class=\"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none hidden open:grid backdrop:bg-black/60 backdrop:backdrop-blur-sm\">\n\n  <div data-slot=\"dialog-header\" class=\"flex flex-col gap-1.5 text-left\">\n    <h2 id=\"confirm-delete-title\" data-slot=\"dialog-title\"\n        class=\"text-lg leading-none font-semibold\">\n      Delete item?\n    </h2>\n    <p id=\"confirm-delete-description\" data-slot=\"dialog-description\"\n       class=\"text-sm text-muted-foreground\">\n      This action cannot be undone. The item will be permanently removed.\n    </p>\n  </div>\n\n  <div data-slot=\"dialog-body\" class=\"text-sm text-foreground\">\n    You're about to delete <strong>Untitled draft</strong>.\n  </div>\n\n  <div data-slot=\"dialog-footer\"\n       class=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n    <button type=\"button\" data-dialog-close=\"true\"\n            class=\"inline-flex h-9 items-center justify-center rounded-md border bg-background px-4 text-sm font-medium shadow-xs hover:bg-accent\">\n      Cancel\n    </button>\n    <button type=\"button\"\n            hx-post=\"/items/42\" hx-target=\"closest dialog\" hx-swap=\"none\"\n            data-dialog-close=\"true\"\n            class=\"inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 text-sm font-medium text-white hover:bg-destructive/90\">\n      Delete\n    </button>\n  </div>\n\n  <button type=\"button\" data-dialog-close=\"true\" aria-label=\"Close\"\n          class=\"absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-70 transition-opacity hover:bg-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\">\n    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\n      <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n    </svg>\n  </button>\n</dialog>\n\n<!--\n  Native Invoker Commands variant — ZERO JavaScript.\n\n  The platform now opens/closes <dialog> declaratively: command=\"show-modal\"\n  is the equivalent of .showModal(), command=\"close\" of .close(), and\n  command=\"request-close\" fires a cancelable `cancel` event first (so an\n  unsaved-changes guard can preventDefault() it) before close. commandfor\n  points at the dialog id. A button's value is copied into the dialog's\n  returnValue, so the `close` event can tell which control fired.\n  See repos/mdn/files/en-us/web/html/reference/elements/button/index.md:60-74,149-152\n       repos/mdn/files/en-us/web/html/reference/elements/dialog/index.md:55-71\n-->\n\n<button type=\"button\" command=\"show-modal\" commandfor=\"confirm-delete-native\"\n        aria-haspopup=\"dialog\"\n        class=\"inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 text-sm font-medium text-white hover:bg-destructive/90\">\n  Delete item\n</button>\n\n<dialog id=\"confirm-delete-native\"\n        data-slot=\"dialog\"\n        aria-labelledby=\"confirm-delete-native-title\"\n        class=\"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 m-0 w-[calc(100%-2rem)] max-w-lg grid gap-4 rounded-lg border bg-background p-6 text-foreground shadow-lg outline-none hidden open:grid backdrop:bg-black/60 backdrop:backdrop-blur-sm\">\n  <div data-slot=\"dialog-header\" class=\"flex flex-col gap-1.5 text-left\">\n    <h2 id=\"confirm-delete-native-title\" data-slot=\"dialog-title\"\n        class=\"text-lg leading-none font-semibold\">Delete item?</h2>\n  </div>\n  <div data-slot=\"dialog-footer\"\n       class=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n    <!-- value=\"cancel\" / value=\"confirm\" land in dialog.returnValue -->\n    <button type=\"button\" command=\"request-close\" commandfor=\"confirm-delete-native\" value=\"cancel\"\n            class=\"inline-flex h-9 items-center justify-center rounded-md border bg-background px-4 text-sm font-medium shadow-xs hover:bg-accent\">\n      Cancel\n    </button>\n    <button type=\"button\" command=\"close\" commandfor=\"confirm-delete-native\" value=\"confirm\"\n            class=\"inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 text-sm font-medium text-white hover:bg-destructive/90\">\n      Delete\n    </button>\n  </div>\n</dialog>\n"
    }
  ]
}
