- 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

Need Help Making the Switch?
Moving to Next.js and Payload CMS? I offer advisory support on an hourly basis.
Book Hourly AdvisoryRelated Posts:
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:
- Use conditional Tailwind utilities that apply only when
enableProse={false} - Include both list-style and padding utilities — bullets/numbers require both to display properly
- Add supporting CSS rules in your global styles as a fallback
- 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.


