How to Build a Cross‑Platform Rich Text Editor with Quill and Custom Blots

This article explains the fundamentals of rich‑text editors, compares textarea and contenteditable approaches, introduces Quill’s delta format and parchment model, and provides step‑by‑step guidance for creating custom blots, handling media uploads, and processing paste events for seamless cross‑platform rendering.

ELab Team
ELab Team
ELab Team
How to Build a Cross‑Platform Rich Text Editor with Quill and Custom Blots

Editor Introduction

Rich‑text editors are typically implemented using either textarea or contenteditable . The former is simple but limited; the latter, combined with document.execCommand, powers most modern editors but can produce inconsistent HTML across browsers.

Google Docs‑style editors render on a canvas instead of using contenteditable , which is costly and complex.

We will use Quill as an example to demonstrate a cross‑platform editor that supports custom modules.

Basic Concepts

Delta

Delta is a JSON‑based data structure that describes content or content changes as a list of operations ( op). Each op contains exactly one of insert, retain, or delete, and may include optional attributes for formatting.

Example delta representing " Grass the Green ":
{
  ops: [
    { insert: 'Grass', attributes: { bold: true } },
    { insert: ' the ' },
    { insert: 'Green', attributes: { color: '#00ff00' } }
  ]
}

Applying a delta transformation can change formatting or insert new text, e.g., turning the above into "Grass the blue".

Parchment

Parchment is Quill’s document model built from blot objects, analogous to the DOM tree.

<p>
    A paragraph with video.
    <img src="xxx" alt="">
  </p>
  <p>
    <strong>Bold text.</strong>
  </p>

Basic blot types include:

{
  ShadowBlot,
  ContainerBlot,
  FormatBlot,
  LeafBlot,
  ScrollBlot,
  BlockBlot,
  InlineBlot,
  TextBlot,
  EmbedBlot,
}

Practical Application

Data Flow

In a typical workflow, the editor generates delta data, which is then parsed by platform‑specific renderers to produce the final display.

Data Structure

A robust content model separates media (images, videos, custom blocks) from the delta string and references them by ID, simplifying maintenance and cross‑platform rendering.

interface ItemContent {
    text?: string;          // delta string
    videoList?: Video[];
    imageList?: Image[];
    customList?: Custom[]; // e.g., polls, ads, questionnaires
}

Standard delta operations include plain text, formatted text, lists, embeds for images/videos, and indentation.

// Example of an image embed
{ insert: { image: '${image_uri}' } }

Image/Video Mixed Layout

Uploads should display a placeholder while the file is being uploaded, then replace it with the real media.

Custom Blot

Custom blots encapsulate complex functionality (e.g., charts) into a single reusable unit.

import Quill from 'quill';

enum MediaType { Image = 'image', Video = 'video' }

interface UploadingType { type: MediaType; uid: string }

const BlockEmbed = Quill.import('blots/block/embed');

class Uploading extends BlockEmbed {
  static _value: Record<string, UploadingType> = {};
  static create(value: UploadingType) {
    const ELEMENT_SIZE = 60;
    const node = super.create();
    this._value[value.uid] = value;
    node.contentEditable = false;
    node.style.width = `${ELEMENT_SIZE}px`;
    node.style.height = `${ELEMENT_SIZE}px`;
    node.style.backgroundImage = 'url(placeholder)';
    node.style.backgroundSize = 'cover';
    node.setAttribute('data-uid', value.uid);
    return node;
  }
  static value(v) { return this._value[v.dataset?.uid]; }
}

Uploading.blotName = 'uploading';
Uploading.tagName = 'div';
export default Uploading;

Register the blot and insert it via quill.insertEmbed:

// editor.tsx
Quill.register(Uploading);
quill.insertEmbed(1, 'uploading', { type: 'image', uid: 'xxx' });

Handling Paste Operations

Basic Principle

Listen for the paste event, read clipboardData, process the content, and insert the transformed delta into the editor.

target.addEventListener('paste', (event) => {
  const clipboardData = event.clipboardData || window.clipboardData;
  const text = clipboardData.getData('text');
  const html = clipboardData.getData('text/html');
  // business logic here
  event.preventDefault();
});

Handling Images

Images from the file system provide a File object and can be uploaded directly. Images copied from web pages appear as text/html with temporary URLs; they must be extracted, downloaded, and re‑uploaded before insertion.

Parsing Data

In web environments, the quill-delta-to-html library converts delta to HTML. For mini‑programs, custom parsers are needed to handle media size constraints.

The conversion process normalizes plain text, attaches block metadata, and finally renders each op as a DOM element.

Custom blots can be wrapped in framework components (React, Vue) to keep business logic separate from editor internals.

Conclusion

We have covered the essential workflow for building a rich‑text editor, highlighted key considerations such as delta modeling, custom blots, media handling, and paste processing, and shown how to extend the editor for cross‑platform content rendering.

rich-text-editorDeltacontenteditableQuillcustom blot
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.