react-refractor

A thin React wrapper around refractor/Prism that renders syntax-highlighted code as virtual DOM instead of touching the DOM directly.

Library
npm
v4.0.0
76stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
32/100Needs Attention
Development Activity4
Maintenance20
Community32
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture80
Code Quality78
Innovation68
Learning Curve55

react-refractor is a super-thin React wrapper for refractor, the AST-based syntax highlighter built on Prism. Instead of having Prism manipulate the DOM directly, react-refractor walks the highlighting AST produced by refractor and converts it into React elements, letting React’s own virtual DOM diffing handle updates efficiently. This makes it possible to reuse the exact same highlighting output on both the server and the client, and to only re-render the parts of a code block that actually changed.

The library ships as an ESM-only package for React 18+ and stays deliberately minimal: no bundled languages by default (you register only the Prism/refractor language grammars you need, keeping bundle size down), no automatic stylesheet injection (you bring your own Prism theme), and no dependency on Prism’s DOM-centric plugin system. In exchange for giving up Prism plugins, it gains a custom “markers” feature for highlighting specific lines (or ranges of lines) with your own class names or fully custom React components, plus first-class support for inline rendering and plain-text passthrough.

What You Get

  • A Refractor component that takes value and language props and renders highlighted <pre><code> output
  • A registerLanguage/hasLanguage API for opting in to only the Prism/refractor grammars you actually use, keeping bundle size small
  • A markers prop for highlighting specific lines with a custom class name or a fully custom wrapper component
  • inline and plainText rendering modes for inline code snippets and unhighlighted passthrough
  • Full TypeScript types for props, markers, and the underlying AST node shapes

Common Use Cases

  • Rendering highlighted code blocks in documentation sites and blog platforms built on React
  • Server-side rendering of syntax-highlighted code (e.g. in Next.js or other SSR frameworks) without a DOM
  • Highlighting specific lines of a snippet to draw attention to a diff, error, or change in a tutorial
  • Embedding inline highlighted code fragments within prose, not just full code blocks
  • Building CMS or documentation tooling (e.g. Sanity Studio, which the maintainer also builds) that needs code-block previews

Under The Hood

Architecture The package is a thin adapter layer over refractor’s highlight() output (in src/Refractor.tsx): it calls refractor/core’s highlight(value, language) to get a hast AST, optionally runs it through addMarkers (src/addMarkers.ts) to annotate and re-wrap lines for the markers feature, then walks the resulting tree recursively in mapChildren.ts via createElement calls keyed by depth and index, producing a tree of React elements instead of DOM nodes. The marker-wrapping logic in addMarkers.ts is the most involved piece: it assigns line-start/line-end metadata to every AST node via a custom lineNumberify pass, then for each requested marker line it “unwraps” the tree along shared ancestor paths (using unist-util-visit-parents and unist-util-filter) to isolate and rewrap just the nodes on that line, preserving nesting elsewhere. Swapping out the core highlighting engine (refractor/Prism) would mean rewriting most of this file, since the marker logic depends heavily on hast’s AST shape.

Tech Stack Written in TypeScript, exporting ESM only, and built with @sanity/pkg-utils. It depends on refractor (Prism-based AST highlighter), unist-util-filter, and unist-util-visit-parents for AST traversal — all part of the unified/hast ecosystem rather than anything React-specific. react is a peer dependency (>=18) so consumers control their own React version. The demo app and build tooling use Vite; tests run under Vitest.

Code Quality Tests live under test/ and use Vitest with snapshot testing (__snapshots__/), covering both Refractor rendering (via react-dom/server) and the addMarkers line-wrapping logic against a fixed AST fixture. Type safety is thorough — the library exports its own prop and marker types, and internal functions use type guards (e.g. isReactRefractorMarkerDataWithComponent) rather than casts. ESLint is configured via the sanity shareable config plus prettier for formatting, and posttest runs lint as part of npm test, so CI-style checks are wired into the standard test command even without a separate CI config visible in the shallow clone.

What Makes It Unique Most React syntax-highlighting wrappers either re-run Prism against the live DOM (losing SSR compatibility and fighting React’s reconciliation) or ship their own bespoke highlighting grammars. react-refractor instead treats highlighting as a pure AST transform — reusing refractor’s Prism-grammar output as data, not DOM — which is what makes identical server/client rendering and React-native diffing possible, and is also what enables the line-marker feature to operate as a structural AST rewrite rather than post-hoc DOM manipulation.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search