RichTextProse
A semantic prose renderer for paragraphs, lists, and inline marks. Pass any tree of <p>, <ul>, <strong>, <em>, <code>, and <a> and the component paints it with editorial typography against cb-* tokens.
Distinct from RichTextNodes (token-array renderer in @craft-bits/core/text/rich-text) and Prose (full-document recipe in @craft-bits/core/edu/prose). This is the inline + short-block sweet spot for narration, lesson copy, and callout bodies.
The dot product
The dot product is the smallest brick in the stack. Almost every later result is a dot product wearing a different hat — a projection, a cosine similarity, or a weighted attention score.
- Projecting
vonto a unit vector is a single dot product. - Two vectors are orthogonal exactly when their dot product is zero.
Drop semantic HTML inside and the recipe paints it with editorial typography — serif italic, mono-chip code, accent underlines.
Installation
npx shadcn@latest add https://craftbits.dev/r/rich-text-prose.jsonUsage
import { RichTextProse } from "@craft-bits/core";
<RichTextProse>
<p>
The <strong>dot product</strong> is the smallest brick in the stack.
Almost every later result is a dot product wearing a different hat — a{" "}
<em>projection</em>, a <code>cosine similarity</code>, or a{" "}
<a href="/notes/attention">weighted attention score</a>.
</p>
<ul>
<li>Projection onto a unit vector is a single dot product.</li>
<li>Two vectors are orthogonal exactly when their dot product is zero.</li>
</ul>
</RichTextProse>Prefer the subpath import when you only want this primitive:
import { RichTextProse } from "@craft-bits/core/text/rich-text-prose";Inline vocabulary
| Tag | Renders as | Notes |
|---|---|---|
<p> | body paragraph | First paragraph uses full cb-fg; siblings are cb-fg-muted. |
<strong> / <b> | font-semibold | Emphasis weight tied to cb-fg. |
<em> / <i> | serif italic | Editorial aside. |
<code> | mono + bordered chip | Inline code against cb-bg-muted. |
<a> | accent underline | Visible :focus-visible outline. |
<mark> | tinted highlight | cb-accent/15 background, cb-fg text. |
<small> | 0.875em muted | Footnote-style aside. |
<ul> / <ol> | bulleted / numbered | Markers tinted cb-fg-subtle. |
Variants
Switch the root element when the surrounding layout expects a block-level node:
<RichTextProse as="section">{children}</RichTextProse>Tighten the rhythm for dense reference reading:
<RichTextProse density="compact">{children}</RichTextProse>Promote headings to display-italic serif when the children include short essays:
<RichTextProse headingScale="editorial">
<h2>A short essay</h2>
<p>…</p>
</RichTextProse>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Semantic prose children. |
as | 'div' | 'span' | 'p' | 'section' | 'article' | 'div' | Root element. |
density | 'cozy' | 'compact' | 'cozy' | Inter-block rhythm. |
headingScale | 'inline' | 'editorial' | 'inline' | Heading treatment when children include <h2> / <h3>. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- Pure typography — no interactive controls beyond optional anchors.
- Anchor focus is visible:
:focus-visiblepaints a 2px outline tied to--cb-accent. - Heading levels (
<h2>/<h3>) keep their semantics so screen-reader heading navigation still works. - Body text targets WCAG AA contrast (~4.5:1) against
--cb-bgin both themes. - No motion — no
prefers-reduced-motionfallback required.
Credits
- Extracted from:
AlgoFlashcards(src/platform/ui/RichText.tsx). The source was a single-string parser with 19 lesson-specific matchers (array literals, complexity notation, DSA vocabulary icons, comparison-operator typography) plusstatic/reveal/interactivemotion variants. The library version flips the shape — input is real semantic HTML, motion is the parent's job, and the markup vocabulary collapses to the standard inline tags rendered againstcb-*tokens.