• Home
BuildWithMatija
Get In Touch
  1. Home
  2. Blog
  3. Payload
  4. Fix Tailwind Lists Not Showing: 4-Step RichTextRenderer Fix

Fix Tailwind Lists Not Showing: 4-Step RichTextRenderer Fix

Restore bullets when enableProse=false in Payload CMS using Tailwind utilities and CSS fallback patterns

13th January 2026·Updated on:22nd February 2026·MŽMatija Žiberna·
Payload
Fix Tailwind Lists Not Showing: 4-Step RichTextRenderer Fix

Need Help Making the Switch?

Moving to Next.js and Payload CMS? I offer advisory support on an hourly basis.

Book Hourly Advisory

Related Posts:

  • •Fix Dynamic Tailwind Classes with Class Registries
  • •Build a Scroll-Driven Timeline with GSAP ScrollTrigger
  • •Fix Navbar Shift with scrollbar-gutter: stable — One Line

If you're using Payload CMS with Next.js and Tailwind CSS, you've probably encountered a frustrating issue: your rich text editor renders lists perfectly in the admin interface, but when enableProse={false} on the frontend, the bullet points or numbers simply vanish. The lists render as plain text lines, making your content difficult to read.

I recently built a system with multiple content blocks that needed fine-grained control over list styling. Rather than relying on Tailwind's prose classes globally, I implemented a preventative pattern that ensures lists display correctly regardless of your prose settings. This guide walks you through that exact implementation.

Understanding the Problem

When you set enableProse={true} in your RichTextRenderer component, Tailwind's prose classes handle all list styling automatically—bullets, numbering, padding, margins. Everything works.

But when you set enableProse={false} (which is common for About sections, FAQ answers, and any block-level content where you want precise style control), you lose that automatic styling. The <ul> and <ol> elements render as bare HTML with no visual indicators that they're lists.

This happens because HTML resets default list styles by default, and without Tailwind's prose classes to re-enable them, the browser treats lists like regular <div> elements.

The Correct Implementation Pattern

The key is to apply explicit list styling utilities when prose is disabled. Here's the correct approach:

Step 1: Update Your RichTextRenderer Component

File: src/components/payload/rich-text/rich-text-renderer.tsx

'use client';

import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical';
import { RichText as ConvertRichText, JSXConvertersFunction } from '@payloadcms/richtext-lexical/react';
import { RichTextImage } from '@/components/blocks/richtext-image/richtext-image';
import { cn } from '@/lib/utils';

type Props = {
  data: DefaultTypedEditorState;
  enableGutter?: boolean;
  enableProse?: boolean;
} & React.HTMLAttributes<HTMLDivElement>;

const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => {
  return {
    ...defaultConverters,
    blocks: {
      ...defaultConverters?.blocks,
      richtext_image: ({ node }: { node: any }) => (
        <RichTextImage
          image={node.fields.image}
          caption={node.fields.caption}
          width={node.fields.width}
          alignment={node.fields.alignment}
          rounded={node.fields.rounded}
        />
      ),
    },
  };
};

export function RichTextRenderer({
  data,
  className,
  enableProse = true,
  enableGutter = true,
  ...rest
}: Props) {
  if (!data) {
    return null;
  }

  return (
    <ConvertRichText
      data={data}
      converters={jsxConverters}
      className={cn(
        'payload-richtext',
        {
          'container mx-auto': enableGutter,
          'max-w-none': !enableGutter,
          'prose prose-sm md:prose-base lg:prose-lg dark:prose-invert': enableProse,
          // When prose is disabled, apply explicit list styling utilities
          '[&_p:not(:last-child)]:mb-4 [&_h1]:mb-6 [&_h2]:mb-4 [&_h3]:mb-3 [&_ul]:mb-4 [&_ol]:mb-4 [&_li]:mb-2 [&_li_p]:mb-0 [&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-10 [&_ol]:pl-10':
            !enableProse,
          // When prose is enabled, apply base padding (prose handles list styling)
          '[&_ul]:pl-5 [&_ol]:pl-5': enableProse,
        },
        className,
      )}
      {...rest}
    />
  );
}

The critical part is the conditional className logic. When enableProse={false}, the component now applies three essential utilities:

  • [&_ul]:list-disc — Shows bullet points for unordered lists
  • [&_ol]:list-decimal — Shows numbers for ordered lists
  • [&_ul]:pl-10 [&_ol]:pl-10 — Adds 40px padding so bullets/numbers don't overlap the text

When enableProse={true}, those utilities aren't needed because the prose classes already handle list styling. We only apply base padding (pl-5) in that case.

Step 2: Add Supporting CSS in globals.css

File: src/app/(frontend)/globals.css

/* Override Tailwind Preflight list-style reset for rich text content */
.payload-richtext ul {
  list-style-type: disc !important;
  padding-left: 40px !important;
  margin-left: 0 !important;
}

.payload-richtext ol {
  list-style-type: decimal !important;
  padding-left: 40px !important;
  margin-left: 0 !important;
}

.payload-richtext li {
  margin-bottom: 10px !important;
  display: list-item !important;
}

.payload-richtext ul.list-bullet {
  list-style-type: disc !important;
}

.payload-richtext ol.list-number {
  list-style-type: decimal !important;
}

These CSS rules work alongside the Tailwind utilities. They ensure that even if Tailwind's utilities don't generate or apply correctly, the browser still renders lists properly. The !important flags guarantee these styles override any conflicting resets.

Understanding the Approach

Why both Tailwind utilities and CSS rules? It provides defense-in-depth:

Tailwind utilities ([&_ul]:list-disc) are your primary layer. They apply when Tailwind processes your build and are the modern, maintainable approach.

CSS rules in globals.css are your fallback. They catch edge cases where Tailwind utilities don't generate or apply—particularly useful when the component is part of a custom field in Payload where styling contexts might differ.

The combination of both ensures lists display correctly in every scenario.

Verification and Troubleshooting

Once you've made these changes, verify the implementation:

1. Check Your Component Usage

Anywhere you use RichTextRenderer with enableProse={false}, verify you're passing it rich text data that contains lists.

File: src/components/blocks/about/about-template-1.tsx

<RichTextRenderer
  data={description as any}
  enableProse={false}
  className={cn("text-base md:text-lg leading-relaxed text-foreground", descriptionClass)}
/>

This usage is correct. The component receives rich text data and renders lists without prose styling.

2. Clear Your Next.js Cache

This is the critical preventative step many developers miss. After making these changes, clear the Next.js build cache:

rm -rf .next

Then restart your development server. This ensures Tailwind reprocesses all your arbitrary selectors ([&_ul]:list-disc, etc.) and generates the correct CSS. Without this step, the utilities might not generate even though the code is correct.

3. Visual Inspection

Navigate to a page using your RichTextRenderer with enableProse={false}. Look for:

  • Bullet points visible in unordered lists
  • Numbers visible in ordered lists
  • Proper indentation (bullets/numbers not touching the text)
  • Consistent spacing between list items

If all four conditions are met, your implementation is working correctly.

4. Type Check

Run TypeScript verification to ensure no type errors:

npx tsc --noEmit

Key Takeaways

The pattern for correct list styling in RichTextRenderer is straightforward:

  1. Use conditional Tailwind utilities that apply only when enableProse={false}
  2. Include both list-style and padding utilities — bullets/numbers require both to display properly
  3. Add supporting CSS rules in your global styles as a fallback
  4. Clear your build cache after making changes so utilities generate correctly

This preventative approach means you'll never encounter the frustrating "lists render as plain text" issue. Your lists will display correctly from day one, whether prose is enabled or disabled.

Common Variations

If you need to support different list styles for different blocks, extend the pattern:

// For blocks that need tighter spacing
'[&_li]:mb-1': !enableProse,

// For blocks that need custom indentation
'[&_ul]:pl-12 [&_ol]:pl-12': !enableProse,

// For blocks with nested lists
'[&_ul_ul]:list-circle [&_ul_ul_ul]:list-square': !enableProse,

The underlying principle remains the same: when prose is disabled, you must explicitly tell Tailwind and CSS which list styles to apply.


By following this preventative pattern, you ensure lists display correctly in your rich text content regardless of your prose settings. The combination of explicit Tailwind utilities, supporting CSS rules, and cache clearing creates a robust implementation that works reliably across all your content blocks.

Let me know in the comments if you have questions about implementing this in your own project, and subscribe for more practical development guides.

Thanks, Matija

📚 Comprehensive Payload CMS Guides

Detailed Payload guides with field configuration examples, custom components, and workflow optimization tips to speed up your CMS development process.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

Stay updated! Get our weekly digest with the latest learnings on NextJS, React, AI, and web development tips delivered straight to your inbox.

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in

Fix Dynamic Tailwind Classes with Class Registries
Fix Dynamic Tailwind Classes with Class Registries

20th January 2026

Build a Scroll-Driven Timeline with GSAP ScrollTrigger
Build a Scroll-Driven Timeline with GSAP ScrollTrigger

25th January 2026

Fix Navbar Shift with scrollbar-gutter: stable — One Line
Fix Navbar Shift with scrollbar-gutter: stable — One Line

1st February 2026

Table of Contents

  • Understanding the Problem
  • The Correct Implementation Pattern
  • Step 1: Update Your RichTextRenderer Component
  • Step 2: Add Supporting CSS in globals.css
  • Understanding the Approach
  • Verification and Troubleshooting
  • 1. Check Your Component Usage
  • 2. Clear Your Next.js Cache
  • 3. Visual Inspection
  • 4. Type Check
  • Key Takeaways
  • Common Variations
On this page:
  • Understanding the Problem
  • The Correct Implementation Pattern
  • Verification and Troubleshooting
  • Key Takeaways
  • Common Variations
Build With Matija Logo

Build with Matija

Matija Žiberna

I turn scattered business knowledge into one usable system. End-to-end system architecture, AI integration, and development.

Quick Links

Payload CMS Websites
  • Bespoke AI Applications
  • Projects
  • How I Work
  • Blog
  • Get in Touch

    Have a project in mind? Let's discuss how we can help your business grow.

    Contact me →
    © 2026BuildWithMatija•Principal-led system architecture•All rights reserved