Create @refrakt-md/react renderer with ADR-008 component interface
The Next.js adapter currently renders all runes via renderToHtml() (identity transform only). To support React component overrides — where theme or site authors provide custom React components for specific runes — we need a @refrakt-md/react renderer package that dispatches on typeof/data-rune and provides the ADR-008 framework-native interface (props + named children).
Acceptance Criteria
- New
packages/react/package exists with@refrakt-md/reactname - Exports a
Renderercomponent that recursively renders aRendererNodetree - Renderer dispatches on
typeofattribute to select registered component overrides (falls back to generic HTML element) - Uses
extractComponentInterfacefrom@refrakt-md/transformto partition children into properties, refs, and anonymous content - Property values passed as named React props to component overrides
- Top-level refs passed as render props or named children (React idiom for named slots)
- Original
tagobject passed as prop alongside extracted props (hybrid per ADR-008 Option 4) - Exports a component registry mechanism (similar to
@refrakt-md/svelte's registry) - Exports an
elementsoverride mechanism for HTML element-level overrides (Table, Pre, etc.) - Next.js adapter can use the React renderer instead of
renderToHtml()for pages with component overrides - TypeScript compiles cleanly
- Test suite covers extraction, dispatch, and fallback rendering
Approach
Mirror the architecture of @refrakt-md/svelte:
Renderer.tsx— recursive component that walks the renderable treeregistry.ts—typeofname → React component mappingelements.ts— HTML element name → React component mapping (for Table, Pre, etc.)theme.ts—ReactThemeinterface (manifest + layouts + components + elements)
For named slots, React doesn't have a native slot mechanism. The idiomatic approach is either:
- Named children as props:
<Recipe headline={<div>...</div>} ingredients={<div>...</div>} /> - Render props:
headline={() => <div>...</div>}
Named children as props (ReactNode values) is the most natural React pattern and aligns with how libraries like Radix and Headless UI handle named content regions.
The extraction logic is already framework-agnostic (extractComponentInterface in @refrakt-md/transform). The React-specific work is:
- Converting extracted refs into pre-rendered
ReactNodevalues - Passing them alongside scalar props to the component
- Recursive rendering of the tree (handling both component and HTML element nodes)
References
- ADR-008 — Framework-native component interface for rune overrides
- WORK-117 — Framework-agnostic extraction logic (done)
- WORK-119 — Svelte renderer extraction (done, reference implementation)
packages/svelte/src/Renderer.svelte— Svelte renderer (reference)packages/svelte/src/registry.ts— Svelte component registry (reference)packages/next/— Next.js adapter (consumer of this package)
Resolution
Completed: 2026-04-06
Branch: claude/implement-spec-030-F0LFn
What was done
- Created
packages/react/with full ADR-008 component interface Renderer.tsx— recursive React component dispatching ondata-runeto registered components- Properties extracted as named React props, refs as ReactNode (pre-rendered HTML via dangerouslySetInnerHTML)
- Element overrides: Table (scrollable wrapper), Pre (rf-codeblock structure for behaviors)
- Props passed explicitly (no React Context) for Server Component compatibility
- Registry + elements + ReactTheme type exports
- 21 tests covering dispatch, extraction, overrides, and edge cases
- Updated RefraktContent docs to reference @refrakt-md/react for component override mode
- Fixed adapter package version drift (0.9.0 → 0.9.1) for astro/nuxt/next/eleventy
Notes
- Named refs are rendered to HTML via
renderToHtmland wrapped in<div data-ref="name">withdangerouslySetInnerHTML, matching the Svelte renderer'screateRawSnippetapproach - No React Context used — registry and elements are prop-drilled through the Renderer, ensuring compatibility with Next.js Server Components
- RefraktContent continues to use renderToHtml for layout-aware rendering; the React Renderer is an alternative for content-level rendering with component overrides