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.
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.
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.
Installation
npx shadcn@latest add https://craftbits.dev/r/rich-elements.jsonUsage
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
| Component | Renders as | Notes |
|---|---|---|
RichElements | div / span / section / article / p | Wrapper that scopes editorial typography to its children. |
RichElementHeading | h2 / h3 | The level prop selects the heading. Both use balanced text wrapping. |
RichElementParagraph | p | Body paragraph against cb-fg-muted. |
RichElementStrong | strong | font-semibold against cb-fg. |
RichElementEmphasis | em | Serif italic editorial aside. |
RichElementCode | code | Mono + bordered chip against cb-bg-muted. |
RichElementLink | a | Accent underline + visible :focus-visible ring. |
RichElementMark | mark | Tinted highlight on cb-accent/15. |
RichElementList | ul / ol | The kind prop swaps between disc and decimal. |
RichElementListItem | li | Pairs with RichElementList. |
RichElementBlockquote | blockquote | Left rule plus serif italic. |
RichElementDivider | hr | Hairline 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
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Children 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. |
className | string | — | Merged onto the root via cn(). |
RichElementHeading
| Prop | Type | Default | Description |
|---|---|---|---|
level | 'h2' | 'h3' | 'h2' | Heading level. |
className | string | — | Merged via cn(). |
RichElementList
| Prop | Type | Default | Description |
|---|---|---|---|
kind | 'unordered' | 'ordered' | 'unordered' | Renders <ul> or <ol>. |
className | string | — | Merged 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 byRichElementLink. - 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:
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 aStrongMarkerSVG, a felt-tip marker animation on<mark>, hand-drawn dividers, and a custom inline-SVG bullet driven byframer-motionstagger. The library version keeps the per-element decomposition but drops the project-specific chrome — typography reads fromcb-*tokens, motion is the parent's job, and the components compose cleanly without a stylesheet.