ape-rich-text-renderer
v0.4.0
Published
Lightweight React renderer for TipTap JSON content. Read-side companion of ape-rich-text-editor.
Readme
ape-rich-text-renderer
Lightweight React renderer for TipTap JSONContent. The read-side companion of ape-rich-text-editor.
- No TipTap runtime — only the
JSONContenttype is imported. Apps that just display content do not pay the editor's bundle cost. - No Tailwind required at consumer level — styles ship pre-compiled in
dist/style.css. - Single component, zero config — pass
contentand you're done. - Visual parity with the editor — same heading sizes, list styles, code block rendering. True WYSIWYG.
Install
pnpm add ape-rich-text-renderer
# or
npm install ape-rich-text-renderer
# or
yarn add ape-rich-text-rendererReact 18 or 19 is required (declared as a peer dependency).
Quick start
import { RichTextRenderer, type JSONContent } from 'ape-rich-text-renderer'
import 'ape-rich-text-renderer/style.css'
const content: JSONContent = {
type: 'doc',
content: [
{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Hello' }] },
{ type: 'paragraph', content: [{ type: 'text', text: 'World.' }] },
],
}
export function Article() {
return <RichTextRenderer content={content} />
}The CSS import is required once per app (typically at your entry point).
Loading saved content
In practice, you load JSONContent from your database or API and pass it to the renderer.
import { useEffect, useState } from 'react'
import { RichTextRenderer, type JSONContent } from 'ape-rich-text-renderer'
import 'ape-rich-text-renderer/style.css'
export function Article({ id }: { id: string }) {
const [body, setBody] = useState<JSONContent | null>(null)
useEffect(() => {
fetch(`/api/articles/${id}`)
.then(r => r.json())
.then(article => setBody(article.body))
}, [id])
if (!body) return <p>Cargando…</p>
return (
<article>
<RichTextRenderer content={body} />
</article>
)
}If the saved JSON contains nodes the renderer does not recognize (older schema, custom extensions you do not ship), the renderer renders the children when present and ignores the wrapper — your app does not crash on schema drift.
Props
| Prop | Type | Description |
| ----------- | ------------- | --------------------------------------------------------------------------------------------------------- |
| content | JSONContent | TipTap document JSON (output of editor.getJSON() from the editor package). Required. |
| className | string | Optional extra classes for the wrapping <div>. Useful to constrain width or add prose styling on top. |
Supported nodes
doc, heading (levels 1–3), paragraph, text, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule, image (with width, alignment, float), table, tableRow, tableHeader, tableCell (with backgroundColor, textAlign, colspan, rowspan).
Supported marks
bold, italic, underline, strike, code, textStyle (color), highlight, link.
Round-trip with the editor
The renderer is the read-only counterpart of ape-rich-text-editor. The flow:
┌──────────────┐ editor.getJSON() ┌─────────┐ loadFromDB ┌────────────────┐
│ RichText │ ───────────────────► │ DB │ ──────────────► │ RichText │
│ Editor │ │ JSON │ │ Renderer │
└──────────────┘ └─────────┘ └────────────────┘Visual fidelity is guaranteed because both packages share the same heading sizes, list styles, blockquote borders, code block colors, etc. You can edit a document in the editor, save it, render it, and re-edit it without any drift.
Why a separate package?
Splitting editor and renderer is the difference between ~5 KB and ~250 KB minified. Public-facing pages should never bundle a WYSIWYG.
| | Minified | Min + gzip |
| ------------------------ | -------- | ---------- |
| ape-rich-text-renderer | ~5 KB | ~2 KB |
| ape-rich-text-editor | ~250 KB | ~75 KB |
A typical CMS application bundles both:
ape-rich-text-editoron the admin/draft pages.ape-rich-text-rendereron the public-facing site.
Tailwind compatibility
The lib does not require the consumer to install Tailwind. Styles ship pre-compiled in dist/style.css.
How isolation works
dist/style.css is post-processed at build time so every rule is scoped under the .ape-rte-renderer wrapper class. Importing the stylesheet anywhere in your app will not touch elements outside the renderer:
- Tailwind utilities are wrapped in
:where(.ape-rte-renderer)— specificity stays at (0,1,0), identical to a bare Tailwind utility. The consumer can override any of them by passing their ownclassNameand source order decides the winner. - Tailwind v4's engine-variable reset (
*, :before, :after, ::backdrop { --tw-…: 0 }) is rewritten to apply only to descendants of.ape-rte-renderer, so the consumer's--tw-*variables stay untouched. - The Preflight base reset (margins/font-family on
*,h1-h6,a,button, lists) was already removed in 0.2.1 and is not shipped at all.
Tradeoffs to know
A consumer's global utility rule (e.g. .flex { gap: 99px } in their own CSS) will still affect descendants inside the renderer because :where() has specificity 0. If you need absolute isolation from the consumer's own Tailwind utilities, scope them in your app, not globally.
Server-side rendering
The renderer has no client-only code — it is plain JSX over the JSON tree. Safe to use in Next.js Server Components, Astro, Remix, or any SSR framework. No 'use client' directive required.
// Next.js App Router — Server Component
import { RichTextRenderer } from 'ape-rich-text-renderer'
import 'ape-rich-text-renderer/style.css'
export default async function ArticlePage({ params }: { params: { id: string } }) {
const article = await db.articles.findUnique({ where: { id: params.id } })
return <RichTextRenderer content={article.body} />
}Development
pnpm install
pnpm dev # opens the playground at http://localhost:3334
pnpm build # builds dist/index.{js,cjs,d.ts} and dist/style.css
pnpm type-checkThe dev/ playground renders sample JSONContent covering all supported nodes and marks. Useful to validate visual changes.
Release
Tag-driven release. To publish a new version:
- Bump
versioninpackage.json. git commit -am "chore(release): 0.x.y"git tag v0.x.y && git push --tags
The GitHub Action builds and publishes to npm. Set the NPM_TOKEN repository secret first.
License
UNLICENSED — internal use within APE-SENA-2025.
