A modern, powerful rich text editor built for compatibility and extensibility
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive formatting system supporting inline formats (bold, italic, links), block formats (headers, lists, blockquotes), embed formats (images, videos), and custom attributors for style-based formatting. Quill's formatting system is built on Parchment blots that map directly to DOM elements.
Text-level formatting that can be applied to character ranges within a line.
// Bold formatting
class Bold extends Inline {
static blotName: 'bold';
static tagName: ['STRONG', 'B'];
}
// Italic formatting
class Italic extends Inline {
static blotName: 'italic';
static tagName: ['EM', 'I'];
}
// Underline formatting
class Underline extends Inline {
static blotName: 'underline';
static tagName: 'U';
}
// Strikethrough formatting
class Strike extends Inline {
static blotName: 'strike';
static tagName: ['S', 'STRIKE'];
}
// Inline code formatting
class Code extends Inline {
static blotName: 'code';
static tagName: 'CODE';
}
// Superscript/subscript formatting
class Script extends Inline {
static blotName: 'script';
static tagName: ['SUB', 'SUP'];
static create(value: 'sub' | 'super'): HTMLElement;
static formats(domNode: HTMLElement): string;
}Usage Examples:
// Apply inline formatting
quill.formatText(0, 5, 'bold', true);
quill.formatText(6, 4, 'italic', true);
quill.formatText(11, 3, 'underline', true);
quill.formatText(15, 6, 'strike', true);
quill.formatText(22, 4, 'code', true);
quill.formatText(27, 4, 'script', 'super');
// Multiple formats at once
quill.formatText(0, 10, {
bold: true,
italic: true,
color: '#ff0000'
});
// Remove formatting
quill.formatText(0, 10, {
bold: false,
italic: null // null also removes formatting
});Hyperlink formatting with URL validation and sanitization.
class Link extends Inline {
static blotName: 'link';
static tagName: 'A';
static SANITIZED_URL: 'about:blank';
static PROTOCOL_WHITELIST: ['http', 'https', 'mailto', 'tel', 'sms'];
/**
* Create link element with href attribute
* @param value - URL for the link
* @returns HTMLAnchorElement with href set
*/
static create(value: string): HTMLAnchorElement;
/**
* Get href value from link element
* @param domNode - Link element
* @returns URL from href attribute
*/
static formats(domNode: HTMLAnchorElement): string;
/**
* Sanitize URL to prevent XSS attacks
* @param url - URL to sanitize
* @returns Sanitized URL or fallback URL
*/
static sanitize(url: string): string;
/**
* Format this link blot
* @param name - Format name ('link')
* @param value - URL value or false to remove link
*/
format(name: string, value: string | boolean): void;
}
/**
* Standalone URL sanitization function
* @param url - URL to sanitize
* @param protocols - Allowed protocols (default: Link.PROTOCOL_WHITELIST)
* @returns Sanitized URL
*/
function sanitize(url: string, protocols?: string[]): string;Usage Examples:
// Create links
quill.formatText(0, 5, 'link', 'https://example.com');
quill.formatText(10, 8, 'link', 'mailto:user@example.com');
quill.formatText(20, 12, 'link', 'tel:+1234567890');
// Get current link value
const formats = quill.getFormat(5, 0);
if (formats.link) {
console.log('Link URL:', formats.link);
}
// Remove link
quill.formatText(0, 5, 'link', false);
// Insert link with text
quill.insertText(0, 'Click here', { link: 'https://example.com' });Line-level formatting that affects entire lines or paragraphs.
// Header formatting (H1-H6)
class Header extends Block {
static blotName: 'header';
static tagName: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
/**
* Get header level from DOM element
* @param domNode - Header element
* @returns Header level (1-6)
*/
static formats(domNode: HTMLElement): number;
/**
* Create header element for specified level
* @param value - Header level (1-6)
* @returns Header element
*/
static create(value: number): HTMLElement;
}
// Blockquote formatting
class Blockquote extends Block {
static blotName: 'blockquote';
static tagName: 'blockquote';
}
// Code block formatting
class CodeBlock extends Block {
static blotName: 'code-block';
static tagName: 'DIV';
static className: 'ql-code-block';
static TAB: string;
/**
* Handle tab key in code blocks
* @param range - Current selection range
* @param context - Keyboard context
*/
static handleTab(range: Range, context: any): boolean;
}Usage Examples:
// Apply block formatting
quill.formatLine(0, 1, 'header', 1); // H1
quill.formatLine(20, 1, 'header', 2); // H2
quill.formatLine(40, 1, 'blockquote', true);
quill.formatLine(60, 1, 'code-block', true);
// Multiple lines
quill.formatLine(0, 50, 'header', 3); // Format multiple lines as H3
// Remove block formatting
quill.formatLine(0, 1, 'header', false);
quill.formatLine(20, 1, 'blockquote', false);Ordered and unordered list formatting with nested list support.
// List item formatting
class ListItem extends Block {
static blotName: 'list';
static tagName: 'LI';
/**
* Create list item element
* @param value - List type ('ordered' | 'bullet')
* @returns List item element
*/
static create(value: 'ordered' | 'bullet'): HTMLLIElement;
/**
* Get list type from DOM element
* @param domNode - List item element
* @returns List type string
*/
static formats(domNode: HTMLLIElement): string;
/**
* Register list container with Parchment
*/
static register(): void;
/**
* Format this list item
* @param name - Format name
* @param value - Format value
*/
format(name: string, value: any): void;
}
// List container formatting
class ListContainer extends Container {
static blotName: 'list-container';
static tagName: 'OL';
/**
* Create list container element
* @param value - List type
* @returns List container element
*/
static create(value: string): HTMLOListElement | HTMLUListElement;
}Usage Examples:
// Create bullet list
quill.formatLine(0, 1, 'list', 'bullet');
quill.formatLine(20, 1, 'list', 'bullet');
quill.formatLine(40, 1, 'list', 'bullet');
// Create ordered list
quill.formatLine(0, 1, 'list', 'ordered');
quill.formatLine(20, 1, 'list', 'ordered');
quill.formatLine(40, 1, 'list', 'ordered');
// Remove list formatting
quill.formatLine(0, 1, 'list', false);
// Insert list items
quill.insertText(0, 'First item\n', { list: 'bullet' });
quill.insertText(11, 'Second item\n', { list: 'bullet' });Self-contained elements like images, videos, and formulas.
// Image embed
class Image extends EmbedBlot {
static blotName: 'image';
static tagName: 'IMG';
/**
* Create image element
* @param value - Image URL or configuration object
* @returns Image element
*/
static create(value: string | { src: string; alt?: string; [key: string]: any }): HTMLImageElement;
/**
* Get image attributes
* @param domNode - Image element
* @returns Object with image attributes
*/
static formats(domNode: HTMLImageElement): Record<string, string>;
/**
* Check if URL is valid image
* @param url - URL to check
* @returns True if URL appears to be an image
*/
static match(url: string): boolean;
/**
* Sanitize image URL
* @param url - URL to sanitize
* @returns Sanitized URL
*/
static sanitize(url: string): string;
/**
* Get image source URL
* @param domNode - Image element
* @returns Image source URL
*/
static value(domNode: HTMLImageElement): string;
/**
* Format image attributes
* @param name - Attribute name
* @param value - Attribute value
*/
format(name: string, value: any): void;
}
// Video embed
class Video extends BlockEmbed {
static blotName: 'video';
static tagName: 'IFRAME';
static ATTRIBUTES: string[];
/**
* Create video iframe element
* @param value - Video URL
* @returns Iframe element
*/
static create(value: string): HTMLIFrameElement;
/**
* Get video URL from iframe
* @param domNode - Iframe element
* @returns Video URL
*/
static formats(domNode: HTMLIFrameElement): string;
/**
* Sanitize video URL
* @param url - URL to sanitize
* @returns Sanitized URL
*/
static sanitize(url: string): string;
/**
* Get video URL value
* @param domNode - Iframe element
* @returns Video URL
*/
static value(domNode: HTMLIFrameElement): string;
}
// Formula embed (for mathematical expressions)
class Formula extends EmbedBlot {
static blotName: 'formula';
static tagName: 'SPAN';
static className: 'ql-formula';
/**
* Create formula element
* @param value - LaTeX formula string
* @returns Span element with formula
*/
static create(value: string): HTMLSpanElement;
/**
* Get formula value
* @param domNode - Formula element
* @returns LaTeX formula string
*/
static value(domNode: HTMLSpanElement): string;
/**
* Render formula (requires KaTeX or similar)
* @param value - LaTeX formula
* @returns Rendered formula HTML
*/
html(): string;
}Usage Examples:
// Insert image
quill.insertEmbed(0, 'image', 'https://example.com/image.jpg');
// Insert image with attributes
quill.insertEmbed(0, 'image', {
src: 'https://example.com/image.jpg',
alt: 'Example image',
width: '300'
});
// Insert video
quill.insertEmbed(10, 'video', 'https://youtube.com/watch?v=abc123');
// Insert formula
quill.insertEmbed(20, 'formula', 'E = mc^2');
// Get embed values
const delta = quill.getContents();
delta.ops.forEach(op => {
if (op.insert && typeof op.insert === 'object') {
if (op.insert.image) {
console.log('Image URL:', op.insert.image);
} else if (op.insert.video) {
console.log('Video URL:', op.insert.video);
} else if (op.insert.formula) {
console.log('Formula:', op.insert.formula);
}
}
});Style-based formatting using CSS classes or inline styles.
// Color attributor (style-based)
class ColorStyle extends StyleAttributor {
static attrName: 'color';
static keyName: 'color';
static styleName: 'color';
}
// Color attributor (class-based)
class ColorClass extends ClassAttributor {
static attrName: 'color';
static keyName: 'color';
static className: 'ql-color';
}
// Font attributor
class FontClass extends ClassAttributor {
static attrName: 'font';
static keyName: 'font';
static className: 'ql-font';
}
class FontStyle extends StyleAttributor {
static attrName: 'font';
static keyName: 'font';
static styleName: 'font-family';
}
// Size attributor
class SizeClass extends ClassAttributor {
static attrName: 'size';
static keyName: 'size';
static className: 'ql-size';
}
class SizeStyle extends StyleAttributor {
static attrName: 'size';
static keyName: 'size';
static styleName: 'font-size';
}
// Background attributor
class BackgroundClass extends ClassAttributor {
static attrName: 'background';
static keyName: 'background';
static className: 'ql-bg';
}
class BackgroundStyle extends StyleAttributor {
static attrName: 'background';
static keyName: 'background';
static styleName: 'background-color';
}
// Alignment attributor
class AlignClass extends ClassAttributor {
static attrName: 'align';
static keyName: 'align';
static className: 'ql-align';
}
class AlignStyle extends StyleAttributor {
static attrName: 'align';
static keyName: 'align';
static styleName: 'text-align';
}
// Direction attributor
class DirectionAttribute extends AttributeAttributor {
static attrName: 'direction';
static keyName: 'direction';
static htmlAttribute: 'dir';
}
class DirectionClass extends ClassAttributor {
static attrName: 'direction';
static keyName: 'direction';
static className: 'ql-direction';
}
class DirectionStyle extends StyleAttributor {
static attrName: 'direction';
static keyName: 'direction';
static styleName: 'direction';
}
// Indent attributor
class Indent extends ClassAttributor {
static attrName: 'indent';
static keyName: 'indent';
static className: 'ql-indent';
/**
* Add value to current indent level
* @param node - DOM node
* @param value - Indent level to add
*/
add(node: HTMLElement, value: number): void;
/**
* Compute new indent level
* @param value - Current indent level
* @param delta - Change in indent level
* @returns New indent level
*/
canAdd(value: number, delta: number): boolean;
/**
* Remove indent level
* @param node - DOM node
*/
remove(node: HTMLElement): void;
/**
* Get current indent value
* @param node - DOM node
* @returns Current indent level
*/
value(node: HTMLElement): number;
}Usage Examples:
// Apply color formatting
quill.formatText(0, 10, 'color', '#ff0000');
quill.formatText(15, 5, 'background', '#ffff00');
// Apply font formatting
quill.formatText(0, 10, 'font', 'serif');
quill.formatText(15, 5, 'size', 'large');
// Apply alignment
quill.formatLine(0, 1, 'align', 'center');
quill.formatLine(20, 1, 'align', 'right');
// Apply direction
quill.formatLine(0, 1, 'direction', 'rtl');
// Apply indentation
quill.formatLine(0, 1, 'indent', 1); // Indent once
quill.formatLine(20, 1, 'indent', 2); // Indent twice
// Remove attributor formatting
quill.formatText(0, 10, 'color', false);
quill.formatLine(0, 1, 'align', false);Register custom formats and blots with Quill.
// Register custom formats
Quill.register('formats/highlight', HighlightBlot);
Quill.register('formats/mention', MentionBlot);
// Register multiple formats
Quill.register({
'formats/highlight': HighlightBlot,
'formats/mention': MentionBlot,
'attributors/style/custom': CustomStyleAttributor
});
// Register with overwrite option
Quill.register('formats/bold', CustomBoldBlot, true);Usage Examples:
// Define custom highlight format
class Highlight extends Inline {
static blotName = 'highlight';
static tagName = 'MARK';
static className = 'ql-highlight';
static create(value) {
const node = super.create();
node.setAttribute('data-color', value);
node.style.backgroundColor = value;
return node;
}
static formats(node) {
return node.getAttribute('data-color');
}
}
// Register and use
Quill.register('formats/highlight', Highlight);
const quill = new Quill('#editor', {
formats: ['bold', 'italic', 'highlight']
});
// Use custom format
quill.formatText(0, 10, 'highlight', 'yellow');Table formatting system for creating and managing tabular data with cells, rows, and full table structures.
Tables in Quill follow a hierarchical structure: TableContainer > TableBody > TableRow > TableCell.
Individual table cell format for containing content within table rows.
class TableCell extends Block {
static blotName: 'table';
static tagName: 'TD';
static create(value?: string): HTMLTableCellElement;
static formats(domNode: HTMLElement): string | undefined;
cellOffset(): number;
format(name: string, value: string): void;
row(): TableRow;
rowOffset(): number;
table(): TableContainer | null;
}Table row format for containing table cells in a horizontal layout.
class TableRow extends Container {
static blotName: 'table-row';
static tagName: 'TR';
checkMerge(): boolean;
optimize(context: { [key: string]: any }): void;
rowOffset(): number;
table(): TableContainer | null;
}Table body format for containing table rows within a table structure.
class TableBody extends Container {
static blotName: 'table-body';
static tagName: 'TBODY';
}Main table container format providing table manipulation methods and structure management.
class TableContainer extends Container {
static blotName: 'table-container';
static tagName: 'TABLE';
balanceCells(): void;
cells(column: number): (TableCell | null)[];
deleteColumn(index: number): void;
insertColumn(index: number): void;
insertRow(index: number): void;
rows(): TableRow[];
}Usage Examples:
import { TableCell, TableRow, TableBody, TableContainer } from 'quill';
// Tables are typically created through the module system
// rather than direct format application
const quill = new Quill('#editor', {
modules: {
table: true
}
});
// Use table module methods for table manipulation
const tableModule = quill.getModule('table');
tableModule.insertTable(3, 3); // 3x3 tableInstall with Tessl CLI
npx tessl i tessl/npm-quill