Payload CMS RichText rendering for W1 System with complete CSS module override support.
- âś… Complete CSS Module Override - Following W1Block pattern
- âś… Virtualized Rendering - Optimized for large content
- âś… Flexible Content Patterns - Styled arrays and Lexical nodes
- âś… TypeScript Support - Full type safety
- âś… Server Actions - Next.js App Router compatible
- âś… Locale Support - Multi-language content
- âś… Abort Support - Request cancellation and cleanup
npm install @werk1/w1-system-payload-richtext-renderer// src/app/layout.tsx or src/components/ClientLayout.tsx
import { PayloadConfigProvider } from '@werk1/w1-system-payload-richtext-renderer';
import configPromise from '@/payload.config';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<PayloadConfigProvider
payloadConfig={configPromise}
defaultLocale="en"
getCurrentLocale={() => 'en'} // Optional: dynamic locale
>
{children}
</PayloadConfigProvider>
</body>
</html>
);
}import { useBoundStore } from '@/stores/boundStore';
<PayloadConfigProvider
payloadConfig={configPromise}
getCurrentLocale={() => useBoundStore.getState().ui.currentLocale}
>
{children}
</PayloadConfigProvider>import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';
<PayloadTextContentLoader contentKey="homepage" /><PayloadTextContentLoader
contentKey="large-article"
virtualized={true}
chunkSize={5}
overscan={2}
heightVariant="medium"
/>import customStyles from './custom.module.css';
<PayloadTextContentLoader
contentKey="content"
externalCSSModule={customStyles}
/>This replaces ALL CSS classes with your custom module:
- Container styles
- Heading styles (h1-h6)
- Paragraph, quote, link styles
- Text formatting (bold, italic, etc.)
- Virtualization styles
// components/textbar/content/WeineContent.tsx
import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';
import weineStyles from './weine.module.css';
export function WeineContent() {
return (
<>
<PayloadTextContentLoader
contentKey="weine_text"
externalCSSModule={weineStyles}
virtualized={true}
heightVariant="medium"
/>
<PayloadTextContentLoader
contentKey="weine_list"
externalCSSModule={weineStyles}
virtualized={true}
heightVariant="medium"
/>
</>
);
}import { RichTextRenderer, useRichText } from '@werk1/w1-system-payload-richtext-renderer';
import configPromise from '@/payload.config';
export function CustomComponent() {
const { content, isLoading } = useRichText('my-content', {
payloadConfig: configPromise,
locale: 'de',
timeout: 5000,
});
if (isLoading) return <div>Loading...</div>;
return (
<RichTextRenderer
content={content.arrayContent}
externalCSSModule={customStyles}
/>
);
}// Render only specific array item
<PayloadTextContentLoader
contentKey="menu-sections"
arrayIndex={0} // First section only
externalCSSModule={menuStyles}
/>interface PayloadTextContentLoaderProps {
contentKey?: string;
routeMapping?: Record<string, string>;
virtualized?: boolean;
chunkSize?: number;
overscan?: number;
heightVariant?: 'small' | 'medium' | 'default' | 'large' | 'full';
singleCSSContainerClass?: string;
textLoaderClassName?: string;
arrayIndex?: number;
externalCSSModule?: Record<string, string>;
locale?: string;
collectionName?: string;
timeout?: number;
}interface RichTextRendererProps {
content: RichTextContent;
singleCSSContainerClass?: string;
externalCSSModule?: Record<string, string>;
onError?: (error: Error) => void;
}interface VirtualizedRichTextProps {
content: RichTextContent;
singleCSSContainerClass?: string;
externalCSSModule?: Record<string, string>;
onError?: (error: Error) => void;
chunkSize?: number;
overscan?: number;
heightVariant?: 'small' | 'medium' | 'default' | 'large' | 'full';
}const { content, isLoading, error, arrayLength } = useRichText(contentKey, {
payloadConfig: configPromise,
locale?: string,
timeout?: number,
collectionName?: string,
});Your custom CSS module should export the same class names as the default module:
/* custom.module.css */
/* Container */
.richTextContainer {
max-width: 800px;
padding: 20px;
}
/* Headings */
.h1 {
font-size: 3rem;
font-family: 'YourCustomFont';
color: #333;
}
.h2 { /* ... */ }
.h3 { /* ... */ }
.h4 { /* ... */ }
.h5 { /* ... */ }
.h6 { /* ... */ }
/* Text elements */
.paragraph { /* ... */ }
.bold { /* ... */ }
.italic { /* ... */ }
.underline { /* ... */ }
.strikethrough { /* ... */ }
/* Links */
.link { /* ... */ }
.invalidLink { /* ... */ }
/* Other */
.quote { /* ... */ }
/* Virtualization (if using virtualized mode) */
.virtualizedContainer { /* ... */ }
.virtualizedContainerSmall { /* ... */ }
.virtualizedContainerMedium { /* ... */ }
.virtualizedContainerLarge { /* ... */ }
.virtualizedContainerFull { /* ... */ }
.contentChunk { /* ... */ }
.placeholder { /* ... */ }
.placeholderDefault { /* ... */ }
.placeholderMedium { /* ... */ }
.placeholderLarge { /* ... */ }
/* Default style (fallback) */
.richTextDefaultStyle { /* ... */ }// In your Payload collection
{
richTextArrayWithStyle: [
{
style: 'heroTitle', // Maps to CSS class .heroTitle
content: [/* Lexical nodes */]
},
{
style: 'copy', // Maps to CSS class .copy
content: [/* Lexical nodes */]
}
]
}{
richText: {
root: {
children: [
{ type: 'heading', tag: 'h1', children: [...] },
{ type: 'paragraph', children: [...] },
{ type: 'link', url: '...', children: [...] }
]
}
}
}// components/textbar/content/WeineContent.tsx
import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';
import weineStyles from './weine.module.css';
export function WeineContent() {
return (
<PayloadTextContentLoader
contentKey="weine"
externalCSSModule={weineStyles}
virtualized={true}
heightVariant="medium"
/>
);
}export function MenuContent() {
return (
<>
<PayloadTextContentLoader contentKey="menu_starters" arrayIndex={0} />
<PayloadTextContentLoader contentKey="menu_mains" arrayIndex={1} />
<PayloadTextContentLoader contentKey="menu_desserts" arrayIndex={2} />
</>
);
}export function LocalizedContent() {
const currentLocale = useBoundStore(state => state.ui.currentLocale);
return (
<PayloadTextContentLoader
contentKey="about"
locale={currentLocale}
/>
);
}import { PayloadTextContentLoader } from '@/payload';import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';- Add
PayloadConfigProviderto your root layout - Delete local
src/payload/directory (except config files) - Update all imports
- Build and test
Solution: Wrap your app with PayloadConfigProvider:
<PayloadConfigProvider payloadConfig={configPromise}>
<App />
</PayloadConfigProvider>Solution: Ensure your CSS module exports all required class names. Missing classes will fall back to default styles.
Solution: Content must have more than 10 items to trigger virtualization. Use virtualized={true} prop.
@werk1/w1-system-block- Block foundation with CSS override pattern@werk1/w1-system-media-manager- Media management system@werk1/w1-system-device-info- Device detectionpayload- Payload CMS (peer dependency)
MIT