@pro-laico/richtext
A ready-to-use rich-text block for Atomic Payload: a Lexical editor for writing formatted content in the admin and a renderer that turns it into clean React on your site.
@pro-laico/richtext gives you a rich-text block for your pages. Editors get a ready-to-use Lexical editor for writing formatted content in the Payload admin, and the package ships a renderer that turns that content into clean React on the frontend. It bundles three pieces: the RichTextChild block, the defaultLexical editor preset, and a JSX renderer.
@pro-laico/richtext is a Tool package, a building block of the Atomic Payload stack. It's meant to be used alongside @pro-laico/core and the other @pro-laico/* packages (most easily via the atomic-payload template), not on its own.
Installation
pnpm add @pro-laico/richtextnpm install @pro-laico/richtextyarn add @pro-laico/richtext@payloadcms/richtext-lexical (the Lexical editor package), payload, and react are required peers, so you install the Lexical package alongside this one. Keeping the Lexical dependency here means other Atomic Payload packages don't pay that bundle cost unless they actually use rich text.
Setup
This package isn't a buildConfig plugin you add to the plugins array. Instead it hands you the pieces to wire rich text into your project yourself: an editor preset for the whole admin, a block your editors can drop into a page, and a renderer for the frontend. The atomic-payload template wires all three for you, so the steps below are the standalone path.
@pro-laico/core comes bundled with this plugin — it's a dependency, not a separate install — and provides the shared fields, hooks, and utilities this plugin builds on. See @pro-laico/core → Setup.
Set the default editor
defaultLexical is an opinionated Lexical preset (bold/italic/headings/lists/links and a fixed + inline toolbar) tuned for Atomic Payload. Set it as the editor in your Payload config so every rich-text field in the admin uses it:
import { buildConfig } from 'payload'
import { defaultLexical } from '@pro-laico/richtext/default-lexical'
export default buildConfig({
editor: defaultLexical,
// ...
})The template sets it exactly this way (editor: defaultLexical in its payload.config.ts). Want internal links to point at collections other than pages? Build the preset with the createDefaultLexical factory and pass enabledCollections:
import { createDefaultLexical } from '@pro-laico/richtext/default-lexical'
const editor = createDefaultLexical({ enabledCollections: ['pages', 'posts'] })Add the rich-text block
RichText is a ready-made Payload block (slug RichTextChild) you can drop into any blocks field. It carries a Content tab with a required rich-text area:
import { RichText } from '@pro-laico/richtext'
const PageBlocks = {
name: 'children',
type: 'blocks',
blocks: [RichText],
}To add your own fields to the Content tab (for example a CSS class field) use the createRichTextBlock factory instead of the pre-built RichText value. Pass prependFields / appendFields (the RichTextBlockOptions type) and they're spread at the start / end of the tab:
import type { Block } from 'payload'
import { createRichTextBlock } from '@pro-laico/richtext'
import { ClassNameField } from '@pro-laico/styles'
// a className field at the top of the Content tab feeds the generated stylesheet
const RichTextWithClassName: Block = createRichTextBlock({
prependFields: [ClassNameField({ label: 'Rich Text Atomic Classes', defaultValue: 'prose dark:prose-invert' })],
})Using the @pro-laico/atomic child-blocks plugin? It already includes RichTextChild as one of its default blocks and lets you weave fields in through its blockFields option, so you don't register the block separately. That's how the template adds the prose class field above.
Render it on the frontend
The package ships RichTextChild, a React component that renders the block's serialized Lexical content as JSX (it uses @payloadcms/richtext-lexical/react under the hood and resolves internal links to their href). It's a child-block renderer: the render pipeline hands it the block (whose .richText holds the serialized state) plus a pt pass-through, so you don't pass content into it by hand.
If you're using @pro-laico/atomic, this is already wired up. RichTextChild is registered as the renderer for the RichTextChild block, so rendering a page's children through atomic's RenderChildren draws your rich text automatically:
import { RenderChildren } from '@pro-laico/atomic/children/render'
export default function Page({ page }: { page: { children: any } }) {
return (
<main>
<RenderChildren blocks={page.children} />
</main>
)
}The component sets no styling of its own, so reach for Tailwind's typography (prose) classes, via a class field on the block or your own wrapper, to style the output.
Options
createRichTextBlock(options?) accepts:
Prop
Type
createDefaultLexical(options?) accepts:
Prop
Type
Both factories with every option at its default value, as a working starting point:
import { createDefaultLexical, createRichTextBlock } from '@pro-laico/richtext'
// the same shape as the pre-built `RichText` block and `defaultLexical` preset
createRichTextBlock({
prependFields: [],
appendFields: [],
})
createDefaultLexical({
enabledCollections: ['pages'],
})Exports
Export
Type
RichTextblock
RichTextChild). Drop it into a blocks field to give editors a Lexical content area. It is createRichTextBlock() with no extra fields.Location
@pro-laico/richtextcreateRichTextBlockfunction
RichTextChild block, taking prependFields / appendFields to add your own fields to its Content tab (a CSS class field, project fields, or nothing), so the block carries no CSS dependency of its own.Parameters
options?:RichTextBlockOptionsprependFields (spread at the start of the Content tab) and appendFields (spread at the end). Both default to [].Returns
BlockA Payload blocks field entry (slug RichTextChild) you add to a blocks field.Example
import type { Block } from 'payload'import { createRichTextBlock } from '@pro-laico/richtext'import { ClassNameField } from '@pro-laico/styles'// a className field at the top of the Content tab feeds the generated stylesheetconst RichTextWithClassName: Block = createRichTextBlock({prependFields: [ClassNameField({ label: 'Rich Text Atomic Classes', defaultValue: 'prose dark:prose-invert' })],})const PageBlocks = { name: 'children', type: 'blocks', blocks: [RichTextWithClassName] }Location
@pro-laico/richtextRichTextBlockOptionstype
createRichTextBlock (prependFields / appendFields).Location
@pro-laico/richtextRichTextChildcomponent
@payloadcms/richtext-lexical/react, resolving internal links to their href). Wired into the @pro-laico/atomic render pipeline automatically, so you rarely render it by hand.Parameters
block:RichTextChildThe block document. Its .richText holds the serialized Lexical state.pt:PassThroughsThe render pipeline pass-through props (spreadable props, data attributes). Supplied by RenderChildren.Returns
JSX.ElementA <div> wrapping the rendered rich-text content.Example
import { RenderChildren } from '@pro-laico/atomic/children/render'// RichTextChild is already registered for the RichTextChild block, so a page's// children render their rich text automatically:export default function Page({ page }: { page: { children: any } }) {return ( <main> <RenderChildren blocks={page.children} /> </main>)}Location
@pro-laico/richtextdefaultLexicalconstant
editor. Also available from the /default-lexical subpath.Location
@pro-laico/richtextcreateDefaultLexicalfunction
enabledCollections to point internal links at a different set of collections without copy-pasting the whole preset.Parameters
options?:DefaultLexicalOptionsenabledCollections (the collections the internal-link feature can target). Defaults to ['pages'].Returns
ReturnType<typeof lexicalEditor>A Lexical editor config you set as your Payload config's editor.Example
import { buildConfig } from 'payload'import { createDefaultLexical } from '@pro-laico/richtext/default-lexical'export default buildConfig({// internal links can target both pages and postseditor: createDefaultLexical({ enabledCollections: ['pages', 'posts'] }),// ...})Location
@pro-laico/richtext/default-lexicalDefaultLexicalOptionstype
createDefaultLexical (enabledCollections).Location
@pro-laico/richtext/default-lexicalRelated
@pro-laico/mux-video
Lets editors upload videos in the Payload admin and drop them onto pages. Mux handles the encoding and adaptive streaming, and a ready-made video block renders the player.
@pro-laico/seed
Fill a fresh project with sample content in one click: a SEED DATABASE button in the admin and a guarded POST /api/seed endpoint behind it.