Rich Text Editing
Puck supports rich text editing via the richtext field.
Adding a rich text field
To use rich text, add the richtext field to your component config:
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
},
},
render: ({ body }) => body,
},
},
};Enabling inline editing
Set contentEditable to make the field editable inline:
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
contenEditable: true,
},
},
render: ({ body }) => body,
},
},
};Customizing behavior
The rich text field is built with Tiptap, and comes with several Tiptap extensions pre-configured. Editor behavior can be customized by configuring these extensions.
Disabling functionality
Disable any extension by setting it to false in options:
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
options: {
// Disable bold extension
bold: false,
},
},
},
render: ({ body }) => body,
},
},
};Configuring extensions
Provide configuration options for the included extensions directly to options for further customization:
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
options: {
// Restrict headings to h1 and h2
// https://tiptap.dev/docs/editor/extensions/nodes/heading
heading: { levels: [1, 2] },
},
},
},
render: ({ body }) => body,
},
},
};Customizing the menu bar
Use renderMenu() to customize the menu bar layout using the <RichTextMenu> component:
import { RichTextMenu } from "@measured/puck";
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
renderMenu: () => (
{/* Always wrap in <RichTextMenu> */}
<RichTextMenu>
{/* Group items in <RichTextMenu.Group> */}
<RichTextMenu.Group>
{/* Only include the bold control */}
<RichTextMenu.Bold />
</RichTextMenu.Group>
</RichTextMenu>
),
},
},
render: ({ body }) => body,
},
},
};Hiding a control does not disable the functionality. Users can still use
keyboard shortcuts and other input mechanisms. To disable functionality, use
options.
A full list of controls is available in the included controls API reference.
Adding custom extensions
Configure the extension
Provide additional Tiptap extensions to the tiptap.extensions parameter to introduce new functionality:
import Superscript from "@tiptap/extension-superscript";
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
tiptap: {
extensions: [Superscript],
},
},
},
render: ({ body }) => body,
},
},
};Add a new control
If you’re adding a control for a new extension, first update the tiptap.selector to make the state available to the menu:
import Superscript from "@tiptap/extension-superscript";
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
tiptap: {
extensions: [Superscript],
selector: ({ editor }) => ({
isSuperscript: editor?.isActive("superscript"),
canSuperscript: editor?.can().chain().toggleSuperscript().run(),
}),
},
},
},
render: ({ body }) => body,
},
},
};Then implement a new control with <RichTextMenu.Control> and render it with renderMenu():
import Superscript from "@tiptap/extension-superscript";
import { Superscript } from "lucide-react";
import { RichTextMenu } from "@measured/puck";
const config = {
components: {
Example: {
fields: {
body: {
type: "richtext",
// ...
renderMenu: ({ children, editor, editorState }) => (
<RichTextMenu>
{/* Render the default controls */}
{children}
<RichTextMenu.Group>
<RichTextMenu.Control
icon={<Superscript />}
onClick={() =>
editor?.chain().focus().toggleSuperscript().run()
}
active={editorState?.isSuperscript}
disabled={!editorState?.canSuperscript}
/>
</RichTextMenu.Group>
</RichTextMenu>
),
},
},
render: ({ body }) => body,
},
},
};