Rails::Domternal

A Domternal (ProseMirror-based) rich text editor integration for Rails, shaped as a drop-in replacement for ActionText:

  • has_rich_domternal :body — a dependent rich text model, mirroring has_rich_text.
  • f.domternal_area :body / domternal_content post.body — form and display helpers, mirroring rich_text_area / the <trix-editor> tag pair.
  • <domternal-editor> — a self-registering custom element (no Stimulus dependency) that mounts the editor and syncs a hidden input on every change.
  • Active Storage-backed image uploads, wired through the same data-direct-upload-url / data-blob-url-template convention ActionText uses.
  • rails g domternal:install — detects importmap-rails vs. a Node/Bun bundler and wires up the JS accordingly.

See demo/ for a working Rails 8 app (importmap + Propshaft) with a Post model using has_rich_domternal.

Installation

Add to your Gemfile:

gem "rails-domternal"

Then:

bundle install
bin/rails g domternal:install
bin/rails db:migrate

Usage

class Post < ApplicationRecord
  has_rich_domternal :body, extensions: %i[starter_kit image table]
end
<%= form_with model: @post do |form| %>
  <%= form.domternal_area :body %>
<% end %>

<%= domternal_content @post.body %>

Configure defaults in config/initializers/domternal.rb (generated by the install task) — see Rails::Domternal::Configuration for the full list of options (default extensions, toolbar, theme, sanitizer allowlist, upload limits).

Extensions

Extensions are referenced by symbolic name (:starter_kit, :image, :table, :mention, :markdown, :emoji, :math, :code_block, :toc, :block_controls, :details), resolved server-side by Rails::Domternal::ExtensionRegistry (fails fast on typos) and lazy-loaded client-side by app/assets/javascripts/domternal/extensions.js. Register a custom extension with:

Rails::Domternal.extension_registry.register(:my_extension)
import { registerExtension } from "domternal/extensions"
registerExtension("my_extension", async () => (await import("my-extension-package")).MyExtension)

Toolbar modes

toolbar: (on has_rich_domternal, domternal_area, or the global config) accepts:

  • :full (default) / :minimal — a fixed DomternalToolbar.

  • :notion — no fixed toolbar; instead a selection bubble menu handles inline formatting and a "+" floating menu (shown only via the block handle's button) inserts blocks. Pair this with the :block_controls extension, which supplies the drag handle, block context menu, slash command, smart paste, and keyboard reorder:

    has_rich_domternal :internal_notes, extensions: %i[starter_kit block_controls toc], toolbar: :notion
    
  • false — no chrome at all, just the raw editable surface.

Content styling

rails g domternal:install also generates a baseline typography stylesheet (headings, lists, blockquotes, code, tables — none of which Domternal's own theme opinionates on) scoped to .domternal-content/<domternal-editor>, covering both the live editor and domternal_content read-only output. It's generated once and meant to be edited freely.

By default it autodetects tailwindcss-rails (app/assets/tailwind/application.css) and generates a @apply-based version wired into your existing Tailwind build; otherwise it generates plain CSS under app/assets/stylesheets/. Force either explicitly with rails g domternal:install --stylesheet=tailwind (or =css).

JavaScript delivery

The install generator pins @domternal/* packages from esm.sh by default for importmap apps. Cross-package peer dependencies (@domternal/core, @domternal/pm) are explicitly shared via ?external= params and a @domternal/pm/ prefix pin — omitting this causes ProseMirror's plugin registry to load twice and crash with "Adding different instances of a keyed plugin." For jsbundling/npm apps, the generator installs the real npm packages and copies the gem's JS source into app/javascript/domternal.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt.

To try it against a real app, see demo/ — it references this gem via a path: Gemfile entry, so changes to the gem are picked up immediately.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/getrestful/rails-domternal.

License

The gem is available as open source under the terms of the MIT License.