Hex.pm Version Hex Docs License GitHub Actions Workflow Status

Server-side renderer for Quill (Slab) Delta: keep Delta as your source of truth in storage, rehydrate Quill for editing, and render HTML for display surfaces (web UI, emails, PDFs).

30-Second Start

# 1) Add dependency
# {:delta_html, "~> 0.5"}

# 2) Convert Quill Delta to HTML
DeltaHtml.to_html([
  %{"insert" => "Hello "},
  %{"attributes" => %{"bold" => true}, "insert" => "world"},
  %{"insert" => "!\n"}
])
# => "<p>Hello <strong>world</strong>!</p>"

What This Is Useful For

Typical production workflow with Quill:

  1. Use Quill in your frontend edit form.
  2. Save only the Delta JSON in your backend/database.
  3. When user edits again, load that Delta back into Quill (rehydrate editor state).
  4. When rendering rich text (web UI, emails, PDFs), convert stored Delta to HTML with DeltaHtml.

This keeps a single source of truth (Delta) while generating HTML only when you need to display content.

Usage

iex> DeltaHtml.to_html([%{"insert" => "word\n"}])
"<p>word</p>"

# With whitespace preservation
iex> DeltaHtml.to_html([%{"insert" => "a   b\tc\n"}], preserve_whitespace: true)
"<div style=\"white-space: pre-wrap;\"><p>a   b\tc\n</p></div>"

Quill CSS Mode

Use quill_css: true to emit Quill-style classes for block attributes:

iex> DeltaHtml.to_html(
...>   [%{"insert" => "x"}, %{"attributes" => %{"align" => "center", "direction" => "rtl", "indent" => 2}, "insert" => "\n"}],
...>   quill_css: true
...> )
"<p class=\"ql-align-center ql-direction-rtl ql-indent-2\">x</p>"

Note: this mode is intentionally not identical to Quill getSemanticHTML() for all attributes. Quill semantic output commonly uses inline styles for align/direction, while quill_css: true prefers classes for easier reuse of Quill theme CSS.

Supported Features

Inline

  • ✅ Background Color - background
  • ✅ Bold - bold
  • ✅ Color - color
  • ✅ Font - font (only serif and monospace)
  • ✅ Inline Code - code
  • ✅ Italic - italic
  • ✅ Link - link
  • ✅ Size - size (only small, large, and huge)
  • ✅ Strikethrough - strike
  • ✅ Superscript/Subscript - script
  • ✅ Underline - underline

Block

  • ✅ Blockquote - blockquote
  • ✅ Header - header
  • ✅ Indent - indent
  • ✅ List - list
  • ✅ Text Alignment - align
  • ✅ Text Direction - direction
  • ❌ Code Block - code-block
  • ❌ Formula - formula (requires KaTeX)
  • ❌ Image - image
  • ❌ Video - video

Plugins

  • ✅ quill-mention - output as #{denotation_char}#{id}, e.g. +name

Extensibility

There are currently no extension points for additional formats or plugins. The implementation is a fairly short single file, so copying and adapting it is straightforward.

Alternatives

  • Convert in browser
    • quill.getSemanticHTML(0)
    • Con: Need to store Delta and HTML.
    • Con: Need to sanitize HTML on server.
    • Con: Less control over output (separate transform pass on server?), especially for plugins like quill-mention.
  • NIF
  • Markdown instead of Delta/Quill

Quill Parity Harness

The repository includes a JS harness under quill_harness/ that renders Delta with Quill 2 (getSemanticHTML) and feeds parity checks in Elixir tests.

mix quill.setup
mix test

Development Checks

mix precommit

mix precommit runs:

  • mix format --check-formatted
  • mix compile --warnings-as-errors
  • mix credo --strict
  • mix dialyzer
  • mix test --warnings-as-errors

Styler is configured as a formatter plugin and runs through mix format (not as a standalone Mix task).