01 Software

UI

RichTextContent

Lexical rich text renderer

RichTextContent

A component for rendering Payload CMS Lexical rich text. Two variants are provided:

  • RichTextContent — Base component with maximum flexibility
  • StyledRichTextContent — 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/typography plugin.

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

PropTypeDescription
dataSerializedEditorStateLexical editor data (required)
classNamestringCSS class
internalDocToHref(args: { linkNode }) => stringInternal document link conversion function
convertersPartial<JSXConverters>Override default converters
blocksRecord<string, JSXConverter>Custom block components
disableDefaultConvertersbooleanDisable default converters
textStateTextStateConfigTextStateFeature color/style mapping
<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

SlotPropsDescription
Headingtag, children, nodeh1-h6
Paragraphchildren, isEmpty, nodeParagraph
Blockquotechildren, nodeBlockquote
Listtag, listType, children, nodeul/ol
ListItemchildren, hasSubLists, checked?, value?, nodeli
Linkhref, target?, rel?, children, nodeAnchor link
HorizontalRulenode?Horizontal rule
Uploadsrc, alt, width?, height?, mimeType, filename, sizes?, nodeUpload image/file
Tablechildren, nodeTable
TableRowchildren, nodeTable row
TableCelltag, children, colSpan?, rowSpan?, backgroundColor?, nodeCell (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>
    ),
  }}
/>
<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>
    },
  }}
/>

On this page