React Quill Editor
A React Quill editor for React 19 in AdminKit PRO: Quill 2 wrapped in a QuillEditor component, with snow and bubble themes, a custom toolbar and HTML output.
This component is only available in AdminKit PRO. Learn more.
Use QuillEditor when a plain textarea is not enough: for a reply, an article or a product description with bold text, lists and links. It is a rich text editor for AdminKit for React, and it gives you the text as HTML.
The component wraps plain Quill 2 (quill 2.0.3) itself, not react-quill. That is what makes it work with React 19: react-quill calls findDOMNode, which React 19 removed.
You don't control the editor's value from state. defaultValue is the HTML it starts with, and onChange receives the HTML after every change. Quill loads with a dynamic import("quill") when the first editor appears on a page, so Vite keeps it out of the main bundle. Until Quill has loaded, the component renders the editor's markup with Quill's classes, so the card does not change height.
Example
You write the toolbar yourself, as markup in toolbar. It holds ql-formats groups of ql-* buttons and selects, as in Quill's custom toolbar. Put a space between the groups, because the spaces give the toolbar its gaps.
import { QuillEditor } from "@/components/plugins/quill-editor";
export function ReplyEditor() {
return (
<QuillEditor
theme="snow"
placeholder="Write a reply"
toolbar={
<>
<span className="ql-formats">
<button type="button" className="ql-bold" aria-label="Bold" />
<button type="button" className="ql-italic" aria-label="Italic" />
<button type="button" className="ql-underline" aria-label="Underline" />
</span>{" "}
<span className="ql-formats">
<button type="button" className="ql-list" value="ordered" aria-label="Numbered list" />
<button type="button" className="ql-list" value="bullet" aria-label="Bulleted list" />
</span>{" "}
<span className="ql-formats">
<button type="button" className="ql-link" aria-label="Link" />
<button type="button" className="ql-clean" aria-label="Clear formatting" />
</span>
</>
}
/>
);
}Without toolbar, Quill builds the default toolbar of the theme, or the one you describe in modules.toolbar.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
theme | "snow" | "bubble" | "snow" | The toolbar above the editor, or a tooltip on selected text |
toolbar | ReactNode | The toolbar markup | |
modules | Record<string, unknown> | Quill modules | |
placeholder | string | Text shown while the editor is empty | |
defaultValue | string | The initial content, as HTML | |
readOnly | boolean | Shows the content without editing | |
onChange | (html: string, quill: Quill) => void | Called after every change, with the HTML and the Quill instance |
theme, modules, placeholder, defaultValue and readOnly are read once, when Quill starts. className and other props go to the .ql-container element. With the snow theme, the editor area is at least 15rem high. The component is in src/components/plugins/quill-editor.tsx.
Bubble theme
For a lighter look, such as editing text in place, use theme="bubble". It has no fixed toolbar. Instead, the toolbar appears in a tooltip when the user selects text. The component hides the toolbar markup until Quill has moved it into the tooltip.
<QuillEditor
theme="bubble"
defaultValue="<p>Select this text to format it.</p>"
toolbar={
<span className="ql-formats">
<button type="button" className="ql-bold" aria-label="Bold" />
<button type="button" className="ql-italic" aria-label="Italic" />
<button type="button" className="ql-link" aria-label="Link" />
</span>
}
/>In a form
The editor is not a form field. Keep the HTML from onChange in state and send it with the form, for example in a hidden input:
import { useState } from "react";
import { QuillEditor } from "@/components/plugins/quill-editor";
export function ArticleForm({ onSave }: { onSave: (body: string) => void }) {
const [body, setBody] = useState("<p>Draft</p>");
return (
<form
onSubmit={(event) => {
event.preventDefault();
onSave(body);
}}
>
<QuillEditor theme="snow" defaultValue={body} onChange={(html) => setBody(html)} />
<input type="hidden" name="body" value={body} />
<button type="submit" className="btn btn-primary mt-3">Save</button>
</form>
);
}onChange gets Quill's semantic HTML, from quill.getSemanticHTML(). Sanitize it on the server before you show it to other users.
Accessibility
- Quill's toolbar buttons have no text. Give each one an
aria-label, as in the examples. - Quill builds its dropdowns from the
<select>s without a name. The component labels the pickers it recognizes: "Font", "Size", "Heading", "Text color", "Highlight color" and "Alignment". - The editor area is a
contenteditableelement that Quill manages. Put a visible label or heading next to it, since the placeholder disappears once the user types.
Related
- Next.js Quill Editor: the same component in AdminKit for Next.js
- Bootstrap 5 rich text editor: the same plugin in the HTML version
- Form control: Bootstrap's text inputs and textareas
- Validation: validating the rest of the form
- Editors demo
Last updated on
CalendarPro
React FullCalendar in AdminKit PRO: FullCalendar 7 with month, week and day views, drag and drop events and Bootstrap buttons, in light and dark mode.
Vector map
A React vector map with jsVectorMap: the VectorMap component draws a world map with markers in theme colors and restyles it when the color mode changes.