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.

Preview
A bold claim, an italic aside, a snippet of code, the formula f(x), and a link.
Customize
Root element
span
Enabled markup

Installation

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

Usage

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:

TokenRenders asNotes
**text**<strong> font-semibold, foregroundBold.
_text_<em> serif italic, foregroundWord-boundary guard keeps snake_case identifiers safe.
Backtick + text + backtick<code> mono, bg-muted, borderedInline code. Cannot span newlines.
$expr$<span> serif italic, accent-tintedLightweight inline math placeholder.
[label](url)<a> accent underlineHyperlink. 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

PropTypeDefaultDescription
childrenstringrequiredRaw text with inline markup tokens.
as'span' | 'div' | 'p''span'Root element. span keeps the primitive inline.
markupRichTextMarkupall five enabledPer-token opt-out: bold, italic, code, math, link. Disabled tokens fall through as literal text.
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.
  • Math tokens carry data-cb-math="inline" so screen readers / MathML upgraders can target them.
  • No motion — no prefers-reduced-motion fallback 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), with static / reveal / interactive motion 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 with cb-* semantic tokens.