Rich Text
Enable and control rich text rendering and formatting in Polotno
A single text element can hold multiple fonts, sizes, colors, weights, styles, and decorations per span — you can style parts of the text independently. Internally, rich text is stored as an HTML string in element.text.
store.activePage.addElement({
type: 'text',
text: 'Hello <strong>from rich</strong> <u>text</u> <span style="color: red;">support</span>!',
y: 300,
x: store.width / 2 - 200,
fontSize: 80,
width: 400,
});Rich text now works out of the box — there is nothing to enable. As of Polotno 3.x / 4.x, the new text rendering engine is the default and renderer switching is gone. The old setRichTextEnabled toggle is now a no-op, kept only so existing code doesn't break. The legacy unstable_useHtmlTextRender flag and the htmlTextRenderEnabled parameter are deprecated too.
Reading and writing text format
The recommended way to read and change formatting is the headless format API. It works on any text element without touching the editor's internals.
import { getTextFormat, applyTextFormat } from 'polotno/utils/text-format';
// read the effective format of an element
const format = getTextFormat(element);
// each key reports whether the value is uniform or mixed across spans:
// { value, mixed, values }
console.log(format.fontWeight.value); // 'bold' | 'normal' | undefined (if mixed)
console.log(format.fill.mixed); // true when the element uses more than one color
// apply a GLOBAL change — sets the element-level prop and strips inline
// per-span overrides so the new value shows uniformly
applyTextFormat(element, { fontWeight: 'bold' });
// works on an array too (batched into a single undo step)
applyTextFormat(store.selectedElements, { fill: '#ff0000', fontSize: 48 });The formattable keys are fontSize, fontFamily, fill, fontWeight, fontStyle, and textDecoration.
Build a format toolbar (React)
The useTextFormat hook subscribes to the active selection and re-renders on change. When a text element is in edit mode, applyTextFormat applies to the current in-editor selection; otherwise it applies to the whole selected element(s).
import { observer } from 'mobx-react-lite';
import { useTextFormat } from 'polotno/utils/text-format-state';
const FormatBar = observer(({ store }) => {
const { format, enabled, applyTextFormat } = useTextFormat(store);
if (!enabled) return null;
const isBold = format.fontWeight.value === 'bold';
return (
<button
// prevent the in-editor selection from being lost on click
onMouseDown={(e) => e.preventDefault()}
onClick={() =>
applyTextFormat({ fontWeight: isBold ? 'normal' : 'bold' })
}
>
Bold {format.fontWeight.mixed ? '(mixed)' : ''}
</button>
);
});Vue / vanilla JS
For non-React UIs, use getTextFormatState to read once, or observeTextFormatState to subscribe (it fires immediately and on every relevant change, and returns an unsubscribe function).
import {
getTextFormatState,
observeTextFormatState,
} from 'polotno/utils/text-format-state';
// read once
const { format, enabled, applyTextFormat } = getTextFormatState(store);
// or subscribe
const unsubscribe = observeTextFormatState(store, (state) => {
// update your UI from state.format / state.enabled
// call state.applyTextFormat({ ... }) to change formatting
});Font size and inline overrides
A text element has a base fontSize, and individual spans may carry their own inline sizes. How you change the size depends on what you want to happen to those inline sizes:
Make the whole element one uniform size — applyTextFormat sets the base size and strips inline per-span sizes:
import { applyTextFormat } from 'polotno/utils/text-format';
applyTextFormat(element, { fontSize: 24 });Scale every span proportionally — keep the relative differences between spans and scale them all by a factor with the scaleRichTextFontSizesInHtml helper, updating fontSize and text together:
import { scaleRichTextFontSizesInHtml } from 'polotno/utils/rich-text-html';
const factor = 24 / element.fontSize;
element.set({
fontSize: 24,
text: scaleRichTextFontSizesInHtml(element.text, factor),
});Change only the base size, leaving inline sizes untouched — a plain set:
element.set({ fontSize: 24 });Advanced: direct Quill access
In most cases the format API above is all you need. For lower-level control, Polotno uses Quill under the hood. When a text element is in "edit mode" (the user double‑clicks it) you can reach the live editor:
import { quillRef } from 'polotno/canvas/html-element';
// will log observable object with format of current selection
console.log(quillRef.currentFormat);
// will log current Quill editor instance. if text is not in edit mode, it will return null
console.log(quillRef.editor.instance);
<Button
// (!) IMPORTANT to prevent default behaviour
// so the quill editor doesn't lose the focus!
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={() => {
// if you want to change format to bold of current selection:
const quill = quillRef.editor.instance;
const selection = quill.getSelection();
// toggle bold for selection
quill.formatText(
selection.index,
selection.length,
'bold',
!quillRef.currentFormat.bold,
'user'
);
}}
/>In many cases, you may want to change format while a text element is not in edit mode. In that case you can create a Quill instance temporarily in memory → apply changes → save resulted HTML into the element.
import { createQuill, setQuillContent } from 'polotno/canvas/html-element';
// function to create quill instance in memory
const createTempQuill = ({ html }: { html: string }) => {
const el = document.createElement('div');
document.body.appendChild(el);
el.style.display = 'none';
el.style.whiteSpace = 'pre-wrap';
const quill = createQuill(el);
// use setQuillContent function to preserve some history edge cases
setQuillContent(quill, html);
return quill;
};
// remove quill when finished
const removeTempQuill = (quill: any) => {
quill.root.parentElement.remove();
};
// example usage
const quill = createTempQuill({ html: element.text });
quill.setSelection(0, quill.getLength(), 'api');
// remove bold from text
quill.format('bold', false);
const innerHtml = quill.root.innerHTML;
// remove quill from memory
removeTempQuill(quill);
// save HTML back to element
element.set({ text: innerHtml });Here is a full real‑world example of a "bold" format button:
const ToggleButton = observer(
({
active,
globalActive,
// name of the format
format,
element,
disableGlobal,
enableGlobal,
icon,
}: {
active: boolean;
globalActive: boolean;
format: string;
element: TextElementType;
disableGlobal: () => void;
enableGlobal: () => void;
icon: any;
}) => {
return (
<Button
minimal
icon={icon}
active={active}
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={(e) => {
let quill = (window as any).__polotnoQuill;
if (quill) {
const selection = quill.getSelection();
quill.formatText(
selection.index,
selection.length,
format,
!quillRef.currentFormat[format],
'user'
);
if (globalActive) {
disableGlobal();
}
return;
}
// if whole text selected, let's remove bold from inner
quill = createTempQuill({ html: element.text });
quill.setSelection(0, quill.getLength(), 'api');
quill.format(format, false);
const innerHtml = quill.root.innerHTML;
removeTempQuill(quill);
element.set({ text: innerHtml });
if (globalActive) {
disableGlobal();
} else {
enableGlobal();
}
}}
/>
);
}
);
export const TextBold = observer(({ element, store }: any) => {
return (
<ToggleButton
format="bold"
active={
quillRef.currentFormat.bold ||
element.fontWeight === 'bold' ||
element.fontWeight === '700'
}
globalActive={
element.fontWeight === 'bold' || element.fontWeight === '700'
}
element={element}
disableGlobal={() => element.set({ fontWeight: 'normal' })}
enableGlobal={() => element.set({ fontWeight: 'bold' })}
icon={<Bold />}
/>
);
});Define quill editor formats
You can hook into the quill editor to change the list of available formats or define your own:
import { unstable_setQuillFormats } from 'polotno/config';
unstable_setQuillFormats([
// default list of formats
'bold',
'color',
'font',
'italic',
'size',
'strike',
'underline',
'indent',
'list',
'direction',
// add additional format
'font-weight'
]);
// implement a new format with quill API
import Quill from 'quill';
const Inline = Quill.import('blots/inline');
class FontWeightBlot extends Inline {
static create(value: any) {
const node = super.create();
node.style.fontWeight = value;
return node;
}
static formats(node: HTMLElement) {
return node.style.fontWeight;
}
}
FontWeightBlot.blotName = 'font-weight';
FontWeightBlot.tagName = 'span';
Quill.register(FontWeightBlot);Live demo
Common Questions
Do I need to enable rich text?
No. Rich text is the default in Polotno 3.x / 4.x. The old setRichTextEnabled toggle is now a no-op kept only for backwards compatibility.
Can I use rich text with server-side rendering?
Yes. When using Cloud Render API or polotno-node, pass richTextEnabled: true in export options. (The legacy parameter htmlTextRenderEnabled is also supported but deprecated.)
What HTML tags are supported?
Standard formatting tags: <strong>, <em>, <u>, <span>, and inline styles. See Quill documentation for complete details.