diff --git a/app/components/RichTextEditor.tsx b/app/components/RichTextEditor.tsx
new file mode 100644
index 0000000..6f203e1
--- /dev/null
+++ b/app/components/RichTextEditor.tsx
@@ -0,0 +1,253 @@
+import { useEditor, EditorContent } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import Link from "@tiptap/extension-link";
+import { useEffect, useState } from "react";
+
+interface RichTextEditorProps {
+ /** Hidden form field name; the rendered HTML is mirrored into it. */
+ name: string;
+ /** Visible label rendered above the editor. */
+ label: string;
+ /** Initial HTML loaded into the editor. */
+ defaultValue?: string;
+ /** Optional helper text shown below the editor. */
+ helpText?: string;
+ /** Insertable variable tokens shown as quick-insert buttons. */
+ variables?: { token: string; label?: string }[];
+ /** Min editor height in px. */
+ minHeight?: number;
+}
+
+/**
+ * TipTap-based WYSIWYG editor that mirrors its HTML content into a hidden
+ * input so the parent