TipTap Editor Suite — 8 components for building ProseMirror-powered rich text editing experiences with Phoenix LiveView.
This module provides a real TipTap integration following the established
PhiaUI pattern of optional npm dependencies (like PhiaChart with ECharts).
When @tiptap/core is installed and exposed on window.tiptap, the full
TipTap engine is used. Without it, the existing vanilla PhiaEditor
(execCommand-based) provides a graceful fallback.
Architecture
- JSON is canonical — TipTap stores documents as typed JSON trees
- Server-driven toolbar — active marks/nodes pushed from client, toolbar state lives in LiveView assigns (not JS)
- Form integration — hidden input syncs content for standard form submission
- Phoenix events —
on_updateandon_selectionevent names for LiveView handlers
Components
Core
tiptap_editor/1— main editor with toolbar, content area, form integration (PhiaTipTap)tiptap_toolbar/1— server-driven toolbar rendered from active marks/nodes assignstiptap_content/1— read-only rendered content from TipTap JSON
Floating UI
tiptap_bubble_menu/1— selection-aware floating toolbar (PhiaTipTap)tiptap_floating_menu/1— empty-line insertion menu (PhiaTipTap)tiptap_slash_commands/1— "/" command palette (PhiaTipTap)tiptap_mention/1— @ mention suggestions (PhiaTipTap)
Collaboration
tiptap_collab_cursors/1— collaborative cursor indicators (PhiaTipTapCollab)
Setup
# Install TipTap (optional — vanilla fallback without these)
npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-placeholder
# In app.js:
import { Editor } from "@tiptap/core"
import StarterKit from "@tiptap/starter-kit"
import Placeholder from "@tiptap/extension-placeholder"
window.tiptap = { Editor, StarterKit, Placeholder }
# Register hooks:
import PhiaTipTap from "./hooks/tiptap_editor"
let liveSocket = new LiveSocket("/live", Socket, { hooks: { PhiaTipTap } })
Summary
Functions
Selection-aware floating toolbar for TipTap editor.
Collaborative cursor indicators for TipTap editor.
Renders TipTap JSON content as read-only HTML.
Main TipTap-powered rich text editor with toolbar, content area, and form integration.
Empty-line insertion menu for TipTap editor.
Mention suggestions dropdown for TipTap editor.
Slash command palette for TipTap editor.
Server-driven toolbar for TipTap editor.
Functions
Collaborative cursor indicators for TipTap editor.
Renders colored cursor labels for connected users. Requires the TipTap Collaboration and CollaborationCursor extensions plus a Yjs transport (e.g. Phoenix Channel provider).
Example
<.tiptap_collab_cursors
id="cursors-1"
editor_id="my-editor"
cursors={@connected_users}
/>Each cursor should be a map with :name, :color, and optionally :avatar_url.
Attributes
id(:string) (required)editor_id(:string) (required)cursors(:list) - Defaults to[].class(:string) - Defaults tonil.
Renders TipTap JSON content as read-only HTML.
Uses TipTapHelpers.content_to_html/1 for server-side JSON-to-HTML conversion.
Always sanitize content before display.
Examples
<%!-- From JSON map --%>
<.tiptap_content content={@post.body_json} prose_size={:lg} />
<%!-- From JSON string --%>
<.tiptap_content content={Jason.decode!(@post.body)} />Attributes
content(:any) - Defaults tonil.prose_size(:atom) - Defaults to:base. Must be one of:sm,:base, or:lg.class(:string) - Defaults tonil.- Global attributes are accepted.
Main TipTap-powered rich text editor with toolbar, content area, and form integration.
Uses the PhiaTipTap hook which detects window.tiptap at runtime:
- With TipTap: Full ProseMirror engine (JSON document model, transactions, undo/redo)
- Without TipTap: Falls back to vanilla
PhiaEditor(execCommand-based)
Content Flow
[Server: LiveView]
assigns.content = JSON string
push_event("editor:command:my-editor", %{command: "toggleBold"})
|
[Client: PhiaTipTap hook]
editor.getJSON() → pushEvent("editor:update", %{json, html, word_count})
editor.isActive() → pushEvent("editor:selection", %{active_marks, ...})Examples
<%!-- Standalone --%>
<.tiptap_editor id="blog-editor" placeholder="Write..." min_height="400px" />
<%!-- Form-integrated --%>
<.form for={@form} phx-submit="publish">
<.tiptap_editor id="post-editor" field={@form[:body]} />
</.form>
<%!-- With server-driven commands --%>
def handle_event("make_bold", _, socket) do
{:noreply, push_event(socket, "editor:command:post-editor", %{command: "toggleBold"})}
endAttributes
id(:string) (required)field(Phoenix.HTML.FormField) - Defaults tonil.value(:string) - Defaults tonil.placeholder(:string) - Defaults to"Type '/' for commands, or start writing...".min_height(:string) - Defaults to"300px".extensions(:list) - Defaults to[:starter_kit, :placeholder].on_update(:string) - Defaults to"editor:update".on_selection(:string) - Defaults to"editor:selection".toolbar_variant(:atom) - Defaults to:default. Must be one of:default,:floating, or:compact.show_word_count(:boolean) - Defaults totrue.show_find_replace(:boolean) - Defaults tofalse.content_format(:atom) - Defaults to:json. Must be one of:json, or:html.collab_enabled(:boolean) - Enable collaborative editing mode. Defaults tofalse.collab_doc_id(:string) - Document ID for collaboration channel. Defaults tonil.collab_user(:map) - Current user map with :id, :name, :color keys. Defaults tonil.class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
toolbar_extra- Extra toolbar content appended after default groups.
Mention suggestions dropdown for TipTap editor.
Triggered by typing "@". Shows a server-driven list of suggestions that the user can select to insert a mention node.
Example
<.tiptap_mention
id="mention-1"
editor_id="my-editor"
suggestions={@mention_suggestions}
/>Each suggestion should be a map with :id, :label, and optionally :avatar_url.
Attributes
id(:string) (required)editor_id(:string) (required)suggestions(:list) - Defaults to[].class(:string) - Defaults tonil.
Slash command palette for TipTap editor.
Triggered by typing "/" at the start of a line. Renders a filterable list of
block insertion commands. Commands dispatch to the editor via phx-click events.
Example
<.tiptap_slash_commands id="slash-1" editor_id="my-editor">
<:command label="Heading 1" description="Large heading" action="setHeading" icon="heading" />
<:command label="Bullet List" description="Unordered list" action="toggleBulletList" icon="list" />
</.tiptap_slash_commands>Attributes
id(:string) (required)editor_id(:string) (required)class(:string) - Defaults tonil.
Slots
command- Accepts attributes:label(:string) (required)description(:string)icon(:string)action(:string) (required)
Server-driven toolbar for TipTap editor.
Active states are determined by assigns (pushed from client via selection events), not by JavaScript DOM inspection. This keeps toolbar state in LiveView where it belongs.
Example
<.tiptap_toolbar
editor_id="my-editor"
active_marks={@editor_state.active_marks}
active_nodes={@editor_state.active_nodes}
heading_level={@editor_state.heading_level}
/>Attributes
variant(:atom) - Defaults to:default. Must be one of:default,:floating, or:compact.editor_id(:string) (required)active_marks(:list) - Defaults to[].active_nodes(:list) - Defaults to[].heading_level(:any) - Defaults tonil.class(:string) - Defaults tonil.
Slots
extra- Extra toolbar content appended after default groups.