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, mirroringhas_rich_text.f.domternal_area :body/domternal_content post.body— form and display helpers, mirroringrich_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-templateconvention 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 fixedDomternalToolbar. -
: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_controlsextension, 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.