RichText

A declarative inline renderer that takes an array of RichTextToken objects and emits styled spans. The caller has already decided what each fragment means — this primitive is purely the paint pass.

Use it whenever the data already comes pre-tokenised: an LLM token stream, a Slate / Lexical document fragment, a syntax-tree slice, or any case where pushing input back through a parser feels like a step backwards.

Preview
A declarative renderer for pre-tokenised fragments — paint tokens, or wire it up to a Slate document and skip the parser entirely.
Customize
Root element
span
Enabled token types

Installation

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

Usage

import { RichTextNodes, type RichTextToken } from "@craft-bits/core";
 
const tokens: RichTextToken[] = [
  { type: "text", value: "A " },
  { type: "bold", value: "declarative" },
  { type: "text", value: " inline renderer for " },
  { type: "code", value: "tokens" },
  { type: "text", value: " — paint, don't parse." },
];
 
<RichTextNodes tokens={tokens} />

The barrel exports the component as RichTextNodes to keep its name distinct from the string-parsing RichText in @craft-bits/core/edu/rich-text. Import via the subpath if you prefer the shorter RichText name:

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

Token vocabulary

Six general-purpose marks. Unknown token types fall through as plain text, so the vocabulary is forward-compatible.

Token typeRenders asNotes
textraw stringThe default.
bold<strong> font-semiboldEmphasis weight.
italic<em> serif italicSubtle aside.
code<code> mono + bordered chipInline code.
link<a href> accent underlinePair value with href.
emphasis<span> accent underlineDefined-term marker.

Variants

Switch the root element with as when the surrounding layout expects a block-level node:

<RichTextNodes as="p" tokens={tokens} />

Mix tokens freely — order is preserved and React keys are derived from position:

<RichTextNodes
  tokens={[
    { type: "text", value: "See the " },
    { type: "link", value: "RFC", href: "/rfc" },
    { type: "text", value: " for details." },
  ]}
/>

Props

PropTypeDefaultDescription
tokensreadonly RichTextToken[]requiredPre-tokenised fragments rendered in order.
as'span' | 'div' | 'p''span'Root element. span keeps the primitive inline.
classNamestringMerged onto the root via cn().

Accessibility

  • Pure inline typography — no interactive controls beyond the optional <a> element rendered for link tokens.
  • Anchor focus is visible: :focus-visible paints a 2px outline tied to --cb-accent.
  • Emphasis tokens carry data-cb-emphasis="true" so downstream tooling can target defined terms.
  • No motion — no prefers-reduced-motion fallback required.

Credits

  • Extracted from: craftingattention (app/src/components/ui/RichText.tsx). The source was a parser-first engine with 18 ML-specific matchers (tensor shapes, framework calls, Greek-letter math) and a static / reveal / interactive motion axis. The library version flips the shape — input is a token array, motion is the parent's job, and the markup vocabulary is six general-purpose marks rendered against cb-* semantic tokens.