RichText
An inline primitive that parses a single string of lightweight markup tokens into styled spans. Built for short styled runs inside compound primitives — axis labels, definition terms, button labels, captions — where MDX or Prose would be overkill but plain strings are too dull.
Different from Prose, which styles descendant block-level HTML: RichText parses one string into one inline line of styled spans.
code, the formula f(x), and a link.Installation
npx shadcn@latest add https://craftbits.dev/r/rich-text.jsonUsage
import { RichText } from "@craft-bits/core";
<RichText>
{"A **bold** claim, an _italic_ aside, a snippet of `code`, the formula $f(x)$, and a [link](https://example.com)."}
</RichText>The children prop is always a string — that is the load-bearing difference vs. Prose. Pass it as a JSX expression so the markup characters survive intact.
Markup syntax
Five tokens, parsed in a single greedy non-overlapping pass:
| Token | Renders as | Notes |
|---|---|---|
**text** | <strong> font-semibold, foreground | Bold. |
_text_ | <em> serif italic, foreground | Word-boundary guard keeps snake_case identifiers safe. |
| Backtick + text + backtick | <code> mono, bg-muted, bordered | Inline code. Cannot span newlines. |
$expr$ | <span> serif italic, accent-tinted | Lightweight inline math placeholder. |
[label](url) | <a> accent underline | Hyperlink. Highest priority — guards bold/italic from eating the label. |
Tokens cannot nest. Malformed input (unbalanced delimiters) falls through as literal text — the parser never throws.
Variants
Opt out of any token via the markup prop. Disabled tokens render their delimiters as literal characters:
<RichText markup={{ math: false, link: false }}>
{"Keep **bold** and _italic_, but $math$ and [links](https://x.com) render literally."}
</RichText>Switch the root element with as — useful when the surrounding layout expects a block-level node:
<RichText as="p">{"Block-level paragraph with **inline** markup."}</RichText>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | required | Raw text with inline markup tokens. |
as | 'span' | 'div' | 'p' | 'span' | Root element. span keeps the primitive inline. |
markup | RichTextMarkup | all five enabled | Per-token opt-out: bold, italic, code, math, link. Disabled tokens fall through as literal text. |
className | string | — | Merged 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-visiblepaints a 2px outline tied to--cb-accent. - Math tokens carry
data-cb-math="inline"so screen readers / MathML upgraders can target them. - No motion — no
prefers-reduced-motionfallback required.
Credits
- Extracted from:
craftingattention(app/src/components/ui/RichText.tsx). The CA source was a much heavier engine — 18 regex matchers spanning ML-specific syntax (tensor shapes, framework calls, Greek-letter math), withstatic/reveal/interactivemotion variants and module-level caches. The library version keeps the parse-then-render shape but cuts the markup vocabulary down to five general-purpose tokens, drops motion (the parent decides), and replaces project-specific colors withcb-*semantic tokens.