diff --git a/apps/marketing/package.json b/apps/marketing/package.json
index e678cf887..268ac40e9 100644
--- a/apps/marketing/package.json
+++ b/apps/marketing/package.json
@@ -19,6 +19,7 @@
     "@executor-js/plugin-google-discovery": "workspace:*",
     "@executor-js/plugin-graphql": "workspace:*",
     "@executor-js/plugin-openapi": "workspace:*",
+    "@executor-js/react": "workspace:*",
     "@executor-js/sdk": "workspace:*",
     "@tailwindcss/vite": "^4.2.2",
     "astro": "^6.1.3",
diff --git a/apps/marketing/src/components/self-host-contact-modal.tsx b/apps/marketing/src/components/self-host-contact-modal.tsx
new file mode 100644
index 000000000..0af56ab64
--- /dev/null
+++ b/apps/marketing/src/components/self-host-contact-modal.tsx
@@ -0,0 +1,104 @@
+import { Button } from "@executor-js/react/components/button";
+import { CopyButton } from "@executor-js/react/components/copy-button";
+import {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  DialogTitle,
+  DialogTrigger,
+} from "@executor-js/react/components/dialog";
+const TEMPLATE = "Hi Rhys, I'm at {company} and interested in self hosting executor.";
+const EMAIL = "rhys@executor.sh";
+
+export function SelfHostContactModal() {
+  return (
+    <Dialog>
+      <DialogTrigger asChild>
+        <Button
+          variant="link"
+          className="btn-link self-start h-auto p-0 text-current hover:no-underline"
+        >
+          Get in touch →
+        </Button>
+      </DialogTrigger>
+      <DialogContent
+        showCloseButton={false}
+        className="surface-card gap-0 border-0 p-6 sm:max-w-[520px] sm:p-7"
+        style={{
+          background: "var(--color-surface)",
+          color: "var(--color-ink)",
+        }}
+      >
+        <div className="mb-4 flex items-start justify-between gap-4">
+          <DialogTitle className="text-[20px] font-semibold tracking-[-0.01em]">
+            Get in touch
+          </DialogTitle>
+          <DialogClose asChild>
+            <Button
+              variant="ghost"
+              size="icon-sm"
+              aria-label="Close"
+              style={{ color: "var(--color-ink-3)" }}
+            >
+              <svg
+                width="16"
+                height="16"
+                viewBox="0 0 24 24"
+                fill="none"
+                stroke="currentColor"
+                strokeWidth="2"
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                aria-hidden="true"
+              >
+                <line x1="18" y1="6" x2="6" y2="18" />
+                <line x1="6" y1="6" x2="18" y2="18" />
+              </svg>
+            </Button>
+          </DialogClose>
+        </div>
+
+        <p className="mb-3 text-[13.5px] leading-[1.55]" style={{ color: "var(--color-ink-2)" }}>
+          Copy the template, fill in your company, and send it to me.
+        </p>
+
+        <div
+          className="mb-4 rounded-md border p-3"
+          style={{
+            background: "var(--color-surface-2)",
+            borderColor: "var(--color-rule)",
+          }}
+        >
+          <p
+            className="mb-2 font-mono text-[14px] leading-[1.55]"
+            style={{ color: "var(--color-ink)" }}
+          >
+            {TEMPLATE}
+          </p>
+          <div className="flex justify-end">
+            <CopyButton value={TEMPLATE} label="Copy" />
+          </div>
+        </div>
+
+        <div
+          className="flex items-center justify-between gap-3 rounded-md border px-3 py-2"
+          style={{
+            background: "var(--color-surface-2)",
+            borderColor: "var(--color-rule)",
+          }}
+        >
+          <a
+            href={`mailto:${EMAIL}`}
+            className="truncate font-mono text-[14px] hover:underline"
+            style={{ color: "var(--color-ink)" }}
+          >
+            {EMAIL}
+          </a>
+          <CopyButton value={EMAIL} label="Copy" />
+        </div>
+      </DialogContent>
+    </Dialog>
+  );
+}
+
+export default SelfHostContactModal;
diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro
index 78eced851..ce5ad4dd3 100644
--- a/apps/marketing/src/pages/index.astro
+++ b/apps/marketing/src/pages/index.astro
@@ -1,6 +1,7 @@
 ---
 import Layout from "../layouts/Layout.astro";
 import { AnimatedBeamDemo } from "../components/animated-beam-demo";
+import { SelfHostContactModal } from "../components/self-host-contact-modal";
 ---
 
 <Layout>
@@ -272,152 +273,12 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo";
                 self hosted product. If you're a company interested in self
                 hosting get in touch.
               </p>
-              <button
-                type="button"
-                class="btn-link self-start"
-                data-open-modal="self-host-contact"
-                >Get in touch →</button
-              >
+              <SelfHostContactModal client:load />
             </div>
           </div>
         </div>
       </section>
 
-      {/* ─── SELF-HOST CONTACT MODAL ─── */}
-      <dialog
-        id="self-host-contact"
-        class="self-host-modal"
-        aria-labelledby="self-host-contact-title"
-      >
-        <div class="surface-card p-7 max-w-[520px] w-full">
-          <div class="flex items-start justify-between gap-4 mb-5">
-            <h3
-              id="self-host-contact-title"
-              class="text-[20px] font-semibold tracking-[-0.01em] text-ink"
-            >
-              Get in touch
-            </h3>
-            <button
-              type="button"
-              class="text-ink-3 hover:text-ink transition-colors"
-              data-close-modal
-              aria-label="Close"
-            >
-              <svg
-                width="18"
-                height="18"
-                viewBox="0 0 24 24"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-                stroke-linecap="round"
-                stroke-linejoin="round"
-                ><line x1="18" y1="6" x2="6" y2="18"></line><line
-                  x1="6"
-                  y1="6"
-                  x2="18"
-                  y2="18"></line></svg
-              >
-            </button>
-          </div>
-
-          <p class="text-[13.5px] leading-[1.55] text-ink-2 mb-2">
-            Copy the template, fill in your company, and send it over.
-          </p>
-
-          <div class="relative mb-5">
-            <textarea
-              id="self-host-template"
-              class="w-full text-[14px] leading-[1.55] text-ink bg-surface-2 border border-rule rounded-md p-3 pr-12 font-mono resize-none"
-              rows="3"
-              readonly
-              >Hi Rhys, I'm at {company} and interested in self hosting executor.</textarea
-            >
-            <button
-              type="button"
-              class="copy-btn absolute top-2 right-2 p-1.5 rounded text-ink-3 hover:text-ink hover:bg-surface transition-colors"
-              data-copy-target="self-host-template"
-              aria-label="Copy template to clipboard"
-            >
-              <span class="copy-icons" aria-hidden="true">
-                <svg
-                  class="copy-icon-copy"
-                  width="14"
-                  height="14"
-                  viewBox="0 0 24 24"
-                  fill="none"
-                  stroke="currentColor"
-                  stroke-width="2"
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
-                  ><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
-                  ></rect><path
-                    d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
-                  ></path></svg
-                >
-                <svg
-                  class="copy-icon-check"
-                  width="14"
-                  height="14"
-                  viewBox="0 0 24 24"
-                  fill="none"
-                  stroke="currentColor"
-                  stroke-width="2.5"
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
-                  ><polyline points="20 6 9 17 4 12"></polyline></svg
-                >
-              </span>
-            </button>
-          </div>
-
-          <div
-            class="flex items-center justify-between gap-3 bg-surface-2 border border-rule rounded-md px-3 py-2"
-          >
-            <span class="font-mono text-[14px] text-ink truncate"
-              >rhys@executor.sh</span
-            >
-            <button
-              type="button"
-              class="copy-btn flex items-center gap-1.5 text-[13px] text-ink-2 hover:text-ink transition-colors"
-              data-copy="rhys@executor.sh"
-              aria-label="Copy email to clipboard"
-            >
-              <span class="copy-label">Copy</span>
-              <span class="copy-icons" aria-hidden="true">
-                <svg
-                  class="copy-icon-copy"
-                  width="14"
-                  height="14"
-                  viewBox="0 0 24 24"
-                  fill="none"
-                  stroke="currentColor"
-                  stroke-width="2"
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
-                  ><rect x="9" y="9" width="13" height="13" rx="2" ry="2"
-                  ></rect><path
-                    d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
-                  ></path></svg
-                >
-                <svg
-                  class="copy-icon-check"
-                  width="14"
-                  height="14"
-                  viewBox="0 0 24 24"
-                  fill="none"
-                  stroke="currentColor"
-                  stroke-width="2.5"
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
-                  ><polyline points="20 6 9 17 4 12"></polyline></svg
-                >
-              </span>
-            </button>
-          </div>
-        </div>
-      </dialog>
-
       {/* ─── FOOTER ─── */}
       <footer class="border-t border-rule py-10">
         <div
@@ -447,91 +308,23 @@ import { AnimatedBeamDemo } from "../components/animated-beam-demo";
 </Layout>
 
 <script>
-  const flashCopied = (() => {
-    const timers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();
-    return (btn: HTMLElement, ok: boolean) => {
-      btn.dataset.copied = ok ? "true" : "false";
-      const existing = timers.get(btn);
-      if (existing) clearTimeout(existing);
-      timers.set(
-        btn,
-        setTimeout(() => {
-          delete btn.dataset.copied;
-          timers.delete(btn);
-        }, 1500),
-      );
-    };
-  })();
-
   document
     .querySelectorAll<HTMLButtonElement>("button[data-copy]")
     .forEach((btn) => {
-      btn.addEventListener("click", async () => {
-        const text = btn.dataset.copy ?? "";
-        try {
-          await navigator.clipboard.writeText(text);
-          flashCopied(btn, true);
-        } catch {
-          flashCopied(btn, false);
-        }
-      });
-    });
+      let timer: ReturnType<typeof setTimeout> | null = null;
 
-  document
-    .querySelectorAll<HTMLButtonElement>("button[data-copy-target]")
-    .forEach((btn) => {
       btn.addEventListener("click", async () => {
-        const id = btn.dataset.copyTarget ?? "";
-        const target = document.getElementById(id) as
-          | HTMLTextAreaElement
-          | HTMLInputElement
-          | null;
-        const text = target?.value ?? "";
+        const text = btn.dataset.copy ?? "";
         try {
           await navigator.clipboard.writeText(text);
-          flashCopied(btn, true);
+          btn.dataset.copied = "true";
         } catch {
-          flashCopied(btn, false);
+          btn.dataset.copied = "false";
         }
-      });
-    });
-
-  document
-    .querySelectorAll<HTMLButtonElement>("button[data-open-modal]")
-    .forEach((btn) => {
-      btn.addEventListener("click", () => {
-        const id = btn.dataset.openModal ?? "";
-        const dialog = document.getElementById(id) as HTMLDialogElement | null;
-        dialog?.showModal();
-      });
-    });
-
-  document
-    .querySelectorAll<HTMLDialogElement>("dialog.self-host-modal")
-    .forEach((dialog) => {
-      dialog
-        .querySelectorAll<HTMLButtonElement>("button[data-close-modal]")
-        .forEach((btn) => {
-          btn.addEventListener("click", () => dialog.close());
-        });
-
-      dialog.addEventListener("click", (event) => {
-        if (event.target === dialog) dialog.close();
+        if (timer) clearTimeout(timer);
+        timer = setTimeout(() => {
+          delete btn.dataset.copied;
+        }, 1500);
       });
     });
 </script>
-
-<style>
-  dialog.self-host-modal {
-    background: transparent;
-    border: none;
-    padding: 0;
-    max-width: 92vw;
-    width: 520px;
-    color: inherit;
-  }
-  dialog.self-host-modal::backdrop {
-    background: rgba(0, 0, 0, 0.5);
-    backdrop-filter: blur(4px);
-  }
-</style>
diff --git a/apps/marketing/src/styles/global.css b/apps/marketing/src/styles/global.css
index 93bf010a0..ce42e4072 100644
--- a/apps/marketing/src/styles/global.css
+++ b/apps/marketing/src/styles/global.css
@@ -2,6 +2,9 @@
 
 @source "../**/*.astro";
 @source "../**/*.tsx";
+@source "../../../../packages/react/src/components/dialog.tsx";
+@source "../../../../packages/react/src/components/copy-button.tsx";
+@source "../../../../packages/react/src/components/button.tsx";
 
 @theme {
   --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
diff --git a/bun.lock b/bun.lock
index 64bee3d26..42d63f289 100644
--- a/bun.lock
+++ b/bun.lock
@@ -183,6 +183,7 @@
         "@executor-js/plugin-google-discovery": "workspace:*",
         "@executor-js/plugin-graphql": "workspace:*",
         "@executor-js/plugin-openapi": "workspace:*",
+        "@executor-js/react": "workspace:*",
         "@executor-js/sdk": "workspace:*",
         "@tailwindcss/vite": "^4.2.2",
         "astro": "^6.1.3",
