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.
tokens, or wire it up to a Slate document and skip the parser entirely.Installation
npx shadcn@latest add https://craftbits.dev/r/rich-text.jsonUsage
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 type | Renders as | Notes |
|---|---|---|
text | raw string | The default. |
bold | <strong> font-semibold | Emphasis weight. |
italic | <em> serif italic | Subtle aside. |
code | <code> mono + bordered chip | Inline code. |
link | <a href> accent underline | Pair value with href. |
emphasis | <span> accent underline | Defined-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
| Prop | Type | Default | Description |
|---|---|---|---|
tokens | readonly RichTextToken[] | required | Pre-tokenised fragments rendered in order. |
as | 'span' | 'div' | 'p' | 'span' | Root element. span keeps the primitive inline. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- Pure inline typography — no interactive controls beyond the optional
<a>element rendered forlinktokens. - Anchor focus is visible:
:focus-visiblepaints 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-motionfallback 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 astatic/reveal/interactivemotion 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 againstcb-*semantic tokens.