RichTextContent
Lexical rich text renderer
RichTextContent
A component for rendering Payload CMS Lexical rich text. Two variants are provided:
RichTextContent— Base component with maximum flexibilityStyledRichTextContent— Slot-based headless component
Basic Usage
import { RichTextContent } from '@01.software/sdk/ui/rich-text'
function Article({ post }) {
return (
<article className="prose">
<RichTextContent data={post.content} />
</article>
)
}Styling
Three approaches for styling rendered rich text, from simplest to most flexible:
Tailwind Typography (prose)
The quickest option. Wrap the output in a prose container and customize with Tailwind modifiers.
<article className="prose prose-headings:text-blue-900 prose-a:text-indigo-600 prose-img:rounded-lg">
<RichTextContent data={post.content} />
</article>Requires the
@tailwindcss/typographyplugin.
StyledRichTextContent
For structural control over each element type, use the slot-based StyledRichTextContent component. See the StyledRichTextContent section below.
Custom CSS
For non-Tailwind projects, RichTextContent wraps output in a .payload-richtext container that you can target directly.
.payload-richtext h1,
.payload-richtext h2 { margin-bottom: 0.75em; font-weight: 700; }
.payload-richtext p { line-height: 1.75; margin-bottom: 1em; }
.payload-richtext a { color: #2563eb; text-decoration: underline; }RichTextContent Props
| Prop | Type | Description |
|---|---|---|
data | SerializedEditorState | Lexical editor data (required) |
className | string | CSS class |
internalDocToHref | (args: { linkNode }) => string | Internal document link conversion function |
converters | Partial<JSXConverters> | Override default converters |
blocks | Record<string, JSXConverter> | Custom block components |
disableDefaultConverters | boolean | Disable default converters |
textState | TextStateConfig | TextStateFeature color/style mapping |
Internal Link Handling
<RichTextContent
data={post.content}
internalDocToHref={({ linkNode }) => {
const doc = linkNode.fields.doc?.value
return `/blog/${doc?.slug}`
}}
/>Custom Blocks
<RichTextContent
data={post.content}
blocks={{
Iframe: ({ node }) => <iframe src={node.fields.url} className="w-full" />,
Player: ({ node }) => <ReactPlayer url={node.fields.url} controls />,
}}
/>Text Colors (TextStateFeature)
Render text colors and background colors set via TextStateFeature in the Payload editor.
import { defaultColors } from '@payloadcms/richtext-lexical'
<RichTextContent
data={post.content}
textState={{
color: {
...defaultColors.text,
...defaultColors.background,
},
}}
/>The textState config must match the state passed to TextStateFeature in your Payload config. Both RichTextContent and StyledRichTextContent support this prop.
Converter Override
Directly replace default node rendering.
<RichTextContent
data={post.content}
converters={{
heading: ({ node, nodesToJSX }) => {
const Tag = node.tag
return <Tag className="custom-heading">{nodesToJSX({ nodes: node.children })}</Tag>
},
}}
/>StyledRichTextContent
Radix UI-style headless component. Replace each element with a React component slot.
Basic Usage
import { StyledRichTextContent } from '@01.software/sdk/ui/rich-text'
<StyledRichTextContent
data={post.content}
components={{
Heading: ({ tag: Tag, children }) => (
<Tag className="font-bold mb-4">{children}</Tag>
),
Paragraph: ({ children }) => (
<p className="text-base leading-7 mb-4">{children}</p>
),
}}
/>Available Slots
| Slot | Props | Description |
|---|---|---|
Heading | tag, children, node | h1-h6 |
Paragraph | children, isEmpty, node | Paragraph |
Blockquote | children, node | Blockquote |
List | tag, listType, children, node | ul/ol |
ListItem | children, hasSubLists, checked?, value?, node | li |
Link | href, target?, rel?, children, node | Anchor link |
HorizontalRule | node? | Horizontal rule |
Upload | src, alt, width?, height?, mimeType, filename, sizes?, node | Upload image/file |
Table | children, node | Table |
TableRow | children, node | Table row |
TableCell | tag, children, colSpan?, rowSpan?, backgroundColor?, node | Cell (th/td) |
Unspecified slots use Payload's default converters.
Tailwind CSS Example
<StyledRichTextContent
data={post.content}
components={{
Heading: ({ tag: Tag, children }) => (
<Tag className={Tag === 'h1' ? 'text-4xl font-bold mt-8 mb-4' : 'text-2xl font-semibold mt-6 mb-3'}>
{children}
</Tag>
),
Paragraph: ({ children }) => <p className="text-base leading-7 mb-4">{children}</p>,
Blockquote: ({ children }) => <blockquote className="border-l-4 border-gray-300 pl-4 italic my-4">{children}</blockquote>,
List: ({ tag: Tag, children }) => <Tag className="pl-6 mb-4">{children}</Tag>,
Link: ({ href, children, target, rel }) => (
<a href={href} target={target} rel={rel} className="text-blue-600 underline hover:text-blue-800">{children}</a>
),
Upload: ({ src, alt, width, height }) => (
<img src={src} alt={alt} width={width} height={height} className="rounded-lg my-6" />
),
}}
/>Next.js Image Integration
import Image from 'next/image'
<StyledRichTextContent
data={post.content}
components={{
Upload: ({ src, alt, width, height, mimeType }) => (
mimeType.startsWith('image')
? <Image src={src} alt={alt} width={width ?? 800} height={height ?? 600} className="rounded-lg" />
: <a href={src} download>{alt}</a>
),
}}
/>Auto Anchor Links
<StyledRichTextContent
data={post.content}
components={{
Heading: ({ tag: Tag, children, node }) => {
const text = node.children?.[0]?.text ?? ''
const id = text.toLowerCase().replace(/\s+/g, '-')
return <Tag id={id}>{children}</Tag>
},
}}
/>