Skip to content

volser/quill-delta-renderer

Repository files navigation

quill-delta-renderer

CI npm version license

The modern, type-safe way to render Quill Deltas anywhere — server, client, or CLI.

Render rich Quill content as HTML, React components, or Markdown without needing the Quill editor or a browser DOM. Fully tree-shakeable. Zero dependencies. Written in TypeScript.

Try the live demo

Why this library?

If you're using Quill, you've likely hit one of these walls:

Problem How quill-delta-renderer helps
"I need to render Deltas on the server." Quill requires a browser DOM, so SSR in Next.js, Nuxt, or plain Node is painful. Works in any JavaScript runtime — Node, Deno, Bun, edge functions — no DOM required.
"My React preview re-renders everything on each edit." Using dangerouslySetInnerHTML replaces the entire DOM subtree whenever the Delta changes — no reconciliation, no partial updates. The React renderer returns a native ReactNode tree. React only updates the nodes that actually changed, and you can swap in custom components like <CustomImage> or <LinkPreview>.
"I need to convert Deltas to Markdown." Mapping rich formatting (colors, underlines, tables) to Markdown is surprisingly hard. Three Markdown renderers handle this out of the box — strict, HTML-flavored, or bracket-tagged.
"Adding a custom embed or block type is way too hard." Extending older converters often means monkey-patching or forking. An extensible renderer API with full TypeScript autocomplete — register a custom block handler in a few lines.

At a glance

import { parseQuillDelta } from "quill-delta-renderer";
import { SemanticHtmlRenderer } from "quill-delta-renderer/html";

const delta = {
  ops: [
    { insert: "Hello" },
    { insert: ", world!", attributes: { bold: true } },
    { insert: "\n" },
  ],
};

const html = new SemanticHtmlRenderer().render(parseQuillDelta(delta));
// → '<p>Hello<strong>, world!</strong></p>'

Two lines. Delta in, HTML out. Works the same on the server and in the browser.

Install

npm install quill-delta-renderer

The React renderer is optional — add React only if you use it:

npm install react react-dom

Usage

HTML (server or client)

import { parseQuillDelta } from "quill-delta-renderer";
import { SemanticHtmlRenderer } from "quill-delta-renderer/html";

const ast = parseQuillDelta(delta);
const html = new SemanticHtmlRenderer().render(ast);

React — no dangerouslySetInnerHTML

Render Deltas directly into a React component tree. By default, it outputs standard HTML tags (<p>, <h1>, <strong>, etc.). You can override any block with your own component:

import { parseQuillDelta } from "quill-delta-renderer";
import { ReactRenderer } from "quill-delta-renderer/react";

const renderer = new ReactRenderer({
  components: {
    image: ({ node }) => <CustomImage src={node.data} />,
    video: ({ node }) => <VideoPlayer url={node.data} />,
    paragraph: ({ children, className }) => (
      <p className={className}>{children}</p>
    ),
  },
});

const element = renderer.render(parseQuillDelta(delta));
// Use `element` directly in JSX — it's a ReactNode

Every block type (paragraph, header, blockquote, code-block, list, list-item, image, video, table, table-row, table-cell, formula, mention) can be overridden via components.

Markdown

import { parseQuillDelta } from "quill-delta-renderer";
import { MarkdownRenderer } from "quill-delta-renderer/markdown";

const md = new MarkdownRenderer().render(parseQuillDelta(delta));

Three flavors are available:

Renderer Non-standard formats (underline, color, etc.)
MarkdownRenderer Stripped — strict standard Markdown only
HtmlMarkdownRenderer Rendered as inline HTML (<u>, <sub>, <span>)
BracketMarkdownRenderer Rendered as bracket tags ([STYLE color=red]...[/STYLE])

See the format docs: HTML Markdown · Bracket Markdown

Migrating from quill-delta-to-html

SemanticHtmlRenderer is designed to produce output compatible with quill-delta-to-html, so migration can be as simple as swapping the import:

Before:

import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";

const converter = new QuillDeltaToHtmlConverter(delta.ops, {});
const html = converter.convert();

After:

import { parseQuillDelta } from "quill-delta-renderer";
import { SemanticHtmlRenderer } from "quill-delta-renderer/html";

const html = new SemanticHtmlRenderer().render(parseQuillDelta(delta));

What you gain immediately:

  • Full TypeScript autocomplete for every config option and custom handler
  • Tree-shakeable — import only the renderer you use; unused code is never bundled
  • The same extensibility API works across HTML, React, and Markdown renderers
  • Significantly faster rendering (see Performance)

Compatibility note: Default configuration targets high compatibility with quill-delta-to-html output. For edge cases, see the config options (inlineStyles, classPrefix, linkTarget, etc.) and the configuration reference below. We recommend comparing output on a few representative documents during migration.

Configuration

All renderers accept an optional config object. Every option has a sensible default.

SemanticHtmlRenderer

new SemanticHtmlRenderer({
  classPrefix: "ql", // CSS class prefix (default: 'ql')
  paragraphTag: "p", // Tag for paragraphs (default: 'p')
  linkTarget: "_blank", // Link target attribute (default: '_blank')
  linkRel: "noopener", // Link rel attribute
  inlineStyles: false, // Use inline styles instead of classes
  encodeHtml: true, // HTML-encode text content (default: true)
  customTag: (fmt, node) => {
    /* return a custom tag string or undefined */
  },
});

ReactRenderer

new ReactRenderer({
  classPrefix: "ql",
  linkTarget: "_blank",
  linkRel: "noopener",
  customTag: (fmt, node) => {
    /* return a custom tag string or undefined */
  },
  components: {
    image: ({ node }) => <CustomImage src={node.data} />,
    // override any block type
  },
});

MarkdownRenderer

new MarkdownRenderer({
  bulletChar: "*", // Unordered list character (default: '*')
  fenceChar: "```", // Fenced code block delimiter (default: '```')
  embedHandler: (node) => {
    /* return string for custom embeds */
  },
  embedAttributesHandler: (node) => {
    /* return { key: value } for attribute-only embeds */
  },
});

HtmlMarkdownRenderer and BracketMarkdownRenderer accept the same config.

parseQuillDelta

parseQuillDelta(delta, {
  extraBlockAttributes: { ... },   // Additional block attribute handlers
  blockEmbeds: ['video'],          // Block-level embed types (default: ['video'])
  extraTransformers: [myGrouper],  // Appended after standard transformers
  transformers: [...],             // Replace standard transformers entirely
});

Extending the library

Custom block handling with transformers

A transformer is a function that receives the AST's children array and returns a new array. Use this to group, wrap, or reorganize nodes before rendering:

import type { TNode, Transformer } from "quill-delta-renderer/core";

const imageGallery: Transformer = (children: TNode[]) => {
  // group adjacent images into a gallery container
  return groupImages(children);
};

const ast = parseQuillDelta(delta, {
  extraTransformers: [imageGallery],
});

Handling custom embeds

Quill allows custom embeds (e.g., a custom video player, a mention, or a poll). To render them, first register the embed type in parseQuillDelta, then provide a render handler for your chosen output format:

// 1. Tell the parser about your custom block-level embed
const ast = parseQuillDelta(delta, {
  blockEmbeds: ['video', 'poll'], // 'video' is default, add yours
});

// 2. Handle it in your renderer (React example)
const element = new ReactRenderer({
  components: {
    poll: ({ node }) => <Poll id={node.data.id} />,
  },
}).render(ast);

Writing a custom renderer

For output formats that need HTML-style attribute collection, extend BaseRenderer. For simpler formats (plain text, Markdown-like), extend SimpleRenderer — you only need two methods:

import { SimpleRenderer } from "quill-delta-renderer/core";

class PlainTextRenderer extends SimpleRenderer<string> {
  protected joinChildren(children: string[]) {
    return children.join("");
  }
  protected renderText(text: string) {
    return text;
  }
}

Tree-shakeable imports

Import only what you need — unused renderers are never bundled:

Import path Contents
quill-delta-renderer Barrel export including parseQuillDelta
quill-delta-renderer/core parseDelta, BaseRenderer, SimpleRenderer, types
quill-delta-renderer/common Transformers, sanitizers, shared utilities
quill-delta-renderer/html SemanticHtmlRenderer, QuillHtmlRenderer
quill-delta-renderer/markdown MarkdownRenderer, HtmlMarkdownRenderer, BracketMarkdownRenderer
quill-delta-renderer/react ReactRenderer

Performance

For a single blog post, rendering speed is rarely a bottleneck. Where performance matters is SSR throughput — rendering hundreds of pages per second — and bulk processing large document sets.

In those scenarios, quill-delta-renderer delivers measurable gains:

  • HTML: 2–5× faster than quill-delta-to-html (3.65× on a realistic mixed-content document)
  • React: 1.5–2.9× faster than quill-delta-to-react (2.22× on a realistic mixed-content document)

Full methodology and results: BENCHMARKS.md

API documentation

The library is fully typed. We recommend relying on your IDE's IntelliSense to explore the available configuration options, AST node types, and renderer methods.

Contributing

Contributions are welcome! To get started:

npm install
npm run build
npm test

Please ensure all tests pass and the code is formatted before submitting a pull request.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors