quill-delta-renderer
v0.1.5
Published
A framework-agnostic, AST-based engine for converting Quill Deltas into HTML, Markdown, React components, or any other format.
Downloads
303
Maintainers
Readme
quill-delta-renderer
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.
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-rendererThe React renderer is optional — add React only if you use it:
npm install react react-domUsage
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 ReactNodeEvery 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-htmloutput. 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 testPlease ensure all tests pass and the code is formatted before submitting a pull request.
