RichElements

A composable set of per-element rich-text components plus a RichElements wrapper that scopes editorial typography to its children. Where RichTextProse paints typography via descendant selectors on the consumer's own HTML, RichElements exposes explicit components for each semantic block — useful when an MDX runtime wires individual overrides (strong, em, code, …) or when typography must land outside any scoped region.

Preview

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 v onto a unit vector is a single dot product.
  • Two vectors are orthogonal exactly when their dot product is zero.
A dot product wearing a hat is still a dot product.

Drop the per-element primitives inside the wrapper to opt into editorial typography one mark at a time.

Customize
Root element
div
Alignment
start
Density
Blocks

Installation

npx shadcn@latest add https://craftbits.dev/r/rich-elements.json

Usage

Compose any mix of the per-element primitives inside the wrapper:

import {
  RichElements,
  RichElementHeading,
  RichElementParagraph,
  RichElementStrong,
  RichElementEmphasis,
  RichElementCode,
  RichElementLink,
  RichElementList,
  RichElementListItem,
} from "@craft-bits/core";
 
<RichElements>
  <RichElementHeading level="h2">The dot product</RichElementHeading>
  <RichElementParagraph>
    The <RichElementStrong>dot product</RichElementStrong> is the smallest
    brick in the stack — a <RichElementEmphasis>projection</RichElementEmphasis>,
    a <RichElementCode>cosine similarity</RichElementCode>, or a
    <RichElementLink href="/notes/attention">weighted attention score</RichElementLink>.
  </RichElementParagraph>
  <RichElementList>
    <RichElementListItem>Projection onto a unit vector is a single dot product.</RichElementListItem>
    <RichElementListItem>Two vectors are orthogonal exactly when their dot product is zero.</RichElementListItem>
  </RichElementList>
</RichElements>

Prefer the subpath import when you only want these primitives:

import { RichElements } from "@craft-bits/core/text/rich-elements";

Element vocabulary

ComponentRenders asNotes
RichElementsdiv / span / section / article / pWrapper that scopes editorial typography to its children.
RichElementHeadingh2 / h3The level prop selects the heading. Both use balanced text wrapping.
RichElementParagraphpBody paragraph against cb-fg-muted.
RichElementStrongstrongfont-semibold against cb-fg.
RichElementEmphasisemSerif italic editorial aside.
RichElementCodecodeMono + bordered chip against cb-bg-muted.
RichElementLinkaAccent underline + visible :focus-visible ring.
RichElementMarkmarkTinted highlight on cb-accent/15.
RichElementListul / olThe kind prop swaps between disc and decimal.
RichElementListItemliPairs with RichElementList.
RichElementBlockquoteblockquoteLeft rule plus serif italic.
RichElementDividerhrHairline rule on cb-border-muted.

Variants

Switch the wrapper element when the surrounding layout expects a block-level node:

<RichElements as="section">{children}</RichElements>

Tighten the rhythm for dense reference reading:

<RichElements density="compact">{children}</RichElements>

Centre the scoped region:

<RichElements align="center">{children}</RichElements>

Promote a list to ordered:

<RichElementList kind="ordered">
  <RichElementListItem>First.</RichElementListItem>
  <RichElementListItem>Second.</RichElementListItem>
</RichElementList>

Props

RichElements

PropTypeDefaultDescription
childrenReactNoderequiredChildren rendered inside the scoped region.
as'div' | 'span' | 'p' | 'section' | 'article''div'Root element.
density'cozy' | 'compact''cozy'Inter-block rhythm.
align'start' | 'center' | 'end' | 'justify''start'Inline text alignment.
classNamestringMerged onto the root via cn().

RichElementHeading

PropTypeDefaultDescription
level'h2' | 'h3''h2'Heading level.
classNamestringMerged via cn().

RichElementList

PropTypeDefaultDescription
kind'unordered' | 'ordered''unordered'Renders <ul> or <ol>.
classNamestringMerged via cn().

Every other per-element component accepts the matching HTML element's props plus className, merged via cn().

Accessibility

  • Pure typography — no interactive controls beyond the optional <a> element rendered by RichElementLink.
  • Anchor focus is visible: :focus-visible paints 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-bg in both themes.
  • No motion — no prefers-reduced-motion fallback required.

Credits

  • Extracted from: terminal-dreams (src/components/ui/RichElements.tsx). The source wired MDX overrides for <strong>, <em>, <mark>, <a>, <blockquote>, <hr>, <ul>, <ol>, and <li> against a bespoke .rich-* stylesheet — including a StrongMarker SVG, a felt-tip marker animation on <mark>, hand-drawn dividers, and a custom inline-SVG bullet driven by framer-motion stagger. The library version keeps the per-element decomposition but drops the project-specific chrome — typography reads from cb-* tokens, motion is the parent's job, and the components compose cleanly without a stylesheet.