Rich Editor Suite — 8 components for building rich text editing experiences with Phoenix LiveView.
This module provides PhiaUI's native rich text editor integration using the
vanilla PhiaEditor engine (zero npm dependencies). All formatting is handled
via document.execCommand, the Selection API, and DOM manipulation.
Architecture
- JSON is canonical — documents stored 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
rich_editor/1— main editor with toolbar, content area, form integration (PhiaRichEditor)rich_toolbar/1— server-driven toolbar rendered from active marks/nodes assignsrich_content/1— read-only rendered content from editor JSON
Floating UI
rich_bubble_menu/1— selection-aware floating toolbar (PhiaRichEditor)rich_floating_menu/1— empty-line insertion menu (PhiaRichEditor)rich_slash_commands/1— "/" command palette (PhiaRichEditor)rich_mention/1— @ mention suggestions (PhiaRichEditor)
Collaboration
collab_cursors/1— collaborative cursor indicators (PhiaCollab)
Setup
# In app.js (zero npm dependencies needed):
import PhiaRichEditor from "./hooks/phia_rich_editor"
let liveSocket = new LiveSocket("/live", Socket, { hooks: { PhiaRichEditor } })
Summary
Functions
Collaborative cursor indicators for the rich editor.
Selection-aware floating toolbar for the rich editor.
Renders editor JSON content as read-only HTML.
Main rich text editor with toolbar, content area, and form integration.
Empty-line insertion menu for the rich editor.
Mention suggestions dropdown for the rich editor.
Slash command palette for the rich editor.
Server-driven toolbar for the rich editor.
Functions
Collaborative cursor indicators for the rich editor.
Renders colored cursor labels for connected users.
Example
<.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 editor JSON content as read-only HTML.
Uses EditorContent.content_to_html/1 for server-side JSON-to-HTML conversion.
Always sanitize content before display.
Examples
<%!-- From JSON map --%>
<.rich_content content={@post.body_json} prose_size={:lg} />
<%!-- From JSON string --%>
<.rich_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 rich text editor with toolbar, content area, and form integration.
Uses the PhiaRichEditor hook which initializes the vanilla PhiaEditor
engine (execCommand-based, zero npm dependencies).
Content Flow
[Server: LiveView]
assigns.content = JSON string
push_event("editor:command:my-editor", %{command: "toggleBold"})
|
[Client: PhiaRichEditor hook]
editor.getHTML() → pushEvent("editor:update", %{json, html, word_count})
editor.isActive() → pushEvent("editor:selection", %{active_marks, ...})Examples
<%!-- Standalone --%>
<.rich_editor id="blog-editor" placeholder="Write..." min_height="400px" />
<%!-- Form-integrated --%>
<.form for={@form} phx-submit="publish">
<.rich_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 the rich editor.
Triggered by typing "@". Shows a server-driven list of suggestions.
Example
<.rich_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 the rich editor.
Triggered by typing "/" at the start of a line.
Example
<.rich_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" />
</.rich_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 the rich editor.
Active states are determined by assigns (pushed from client via selection events), not by JavaScript DOM inspection.
Example
<.rich_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.