Renders HTML produced by the rich text editor with consistent typography.
Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/rich-text-content.jsonInstalls to @/components/ui/rich-text-content.
Manual installation
Copy the source into your project:
import type * as React from 'react'
import { cn } from '@/lib/utils'
interface RichTextContentProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'dangerouslySetInnerHTML' | 'children'> {
html: string
}
/**
* Renders rich text HTML content with proper list, paragraph, and link styles.
* Use this wherever richtext from the Tiptap editor is displayed as output.
*/
export function RichTextContent({ html, className, ...props }: RichTextContentProps) {
return (
<div
data-slot="rich-text-content"
className={cn(
'[&_p]:m-0 [&_p]:w-full [&_p:empty]:min-h-[1em] [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:pl-1 [&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4',
className,
)}
// biome-ignore lint/security/noDangerouslySetInnerHtml: richtext content from editor
dangerouslySetInnerHTML={{ __html: html }}
{...props}
/>
)
}Renders an HTML string produced by a rich text editor (Tiptap, ProseMirror, …) with paragraph, list and link styles applied, so the output looks the same as it did in the editor.
The component uses dangerouslySetInnerHTML — only pass markup you trust or have sanitized server-side.
Usage
import { RichTextContent } from "@/components/ui/rich-text-content"
<RichTextContent html={decision.body} className="text-sm" />Examples
Default
A news article with a paragraph, a bullet list and a link.
Muted
Case notes rendered in a smaller, muted style with className.
Decision Letter
A decision with a numbered list of conditions, inside a card.