npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mdocui/react

v0.6.6

Published

React adapter for mdocUI — Renderer, useRenderer hook, default generative UI components

Readme

@mdocui/react

React adapter for mdocui — streaming Renderer component, useRenderer hook, and 24 theme-neutral UI components for LLM generative UI powered by Markdoc {% %} tag syntax.

Part of the mdocui monorepo.

Install

npm install @mdocui/react @mdocui/core

@mdocui/core is a peer dependency.

Quick Start

import { Renderer, useRenderer, defaultComponents, createDefaultRegistry } from '@mdocui/react'

const registry = createDefaultRegistry()

function Chat() {
  const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })

  // Feed chunks from your LLM stream
  async function onStream(reader: ReadableStreamDefaultReader<string>) {
    reset()
    while (true) {
      const { value, done: streamDone } = await reader.read()
      if (streamDone) break
      push(value)
    }
    done()
  }

  return (
    <Renderer
      nodes={nodes}
      isStreaming={isStreaming}
      onAction={(event) => console.log('action:', event)}
    />
  )
}

useRenderer

Hook that wraps @mdocui/core's StreamingParser. Manages parse state in React and returns reactive AST nodes.

const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })

Options

| Option | Type | Description | |--------|------|-------------| | registry | ComponentRegistry | Component registry from @mdocui/core |

Return value (UseRendererReturn)

| Field | Type | Description | |-------|------|-------------| | nodes | ASTNode[] | Current parsed AST | | meta | ParseMeta | Parse errors, node count, completion status | | isStreaming | boolean | true between the first push and done | | push(chunk) | (chunk: string) => void | Feed a text chunk from the LLM stream | | done() | () => void | Signal the stream has ended; flushes the parser | | reset() | () => void | Clear all state for a new conversation turn |


Renderer

Renders an ASTNode[] tree into React elements using a component map.

<Renderer
  nodes={nodes}
  components={defaultComponents}
  onAction={handleAction}
  isStreaming={isStreaming}
  renderProse={(content, key) => <Markdown key={key}>{content}</Markdown>}
/>

Props (RendererProps)

| Prop | Type | Default | Description | |------|------|---------|-------------| | nodes | ASTNode[] | required | AST from useRenderer or StreamingParser | | components | ComponentMap | all 24 built-ins | Custom components merged on top of defaults (see below) | | onAction | ActionHandler | no-op | Callback for interactive events | | isStreaming | boolean | false | Whether the LLM is still streaming | | registry | ComponentRegistry | — | Optional registry for prop validation warnings (dev aid) | | meta | ParseMeta | — | Parse metadata from useRenderer — enables shimmer for pending components | | renderProse | (content: string, key: string) => ReactNode | built-in SimpleMarkdown | Custom renderer for prose nodes (see below) | | renderPendingComponent | ((tag?: string) => ReactNode) \| null | built-in ComponentShimmer | Custom shimmer during streaming. Pass null to disable | | contextData | Record<string, unknown> | — | App-level data accessible to all components via useMdocUI() | | classNames | Record<string, string> | — | Per-component CSS class overrides |


Prop Validation

Pass a registry to get console.warn messages when the LLM sends invalid props:

const registry = createDefaultRegistry()

<Renderer nodes={nodes} registry={registry} />
// console.warn: [mdocui] <chart> invalid props: Expected string, received number

Validation only runs after streaming ends and only for components registered in the given registry. No performance cost during streaming, no noise for unregistered components.


Streaming Shimmer

When a component tag is being buffered during streaming, the Renderer shows a skeleton placeholder. Pass meta from useRenderer to enable it:

const { nodes, meta, isStreaming } = useRenderer({ registry })

<Renderer nodes={nodes} isStreaming={isStreaming} meta={meta} />

Override the shimmer globally:

<Renderer
  renderPendingComponent={(tag) => <MyShimmer tagName={tag} />}
/>

Disable it:

<Renderer renderPendingComponent={null} />

Prose Rendering

By default, prose nodes are rendered with the built-in SimpleMarkdown component which handles bold, italic, inline code, links, headings (h1-h3), unordered lists, and paragraph breaks — no external dependencies required.

For full GFM support, override with renderProse:

import ReactMarkdown from 'react-markdown'

<Renderer
  nodes={nodes}
  renderProse={(content, key) => <ReactMarkdown key={key}>{content}</ReactMarkdown>}
/>

Any markdown library works -- react-markdown, marked, markdown-it, etc.


Components (22)

All default components are available via defaultComponents and registered in createDefaultRegistry().

Layout (7)

| Component | Tag | Description | |-----------|-----|-------------| | Stack | stack | Vertical or horizontal flex container. Props: direction?, gap?, align? | | Grid | grid | CSS grid layout. Props: cols?, gap? | | Card | card | Bordered container. Props: title?, variant? | | Divider | divider | Horizontal separator line. Self-closing. | | Accordion | accordion | Collapsible section. Props: title, open? | | Tabs | tabs | Tabbed container. Props: labels, active? | | Tab | tab | Single tab panel inside tabs. Props: label |

Interactive (6)

| Component | Tag | Description | |-----------|-----|-------------| | Button | button | Action button. Props: action, label, variant?, disabled? | | ButtonGroup | button-group | Row of buttons. Props: direction? | | Input | input | Text input field. Props: name, label?, placeholder?, type?, required? | | Select | select | Dropdown select. Props: name, label?, options, placeholder?, required? | | Checkbox | checkbox | Toggle checkbox. Props: name, label, checked? | | Form | form | Groups inputs; submits collected state. Props: name, action? |

Data (4)

| Component | Tag | Description | |-----------|-----|-------------| | Chart | chart | Bar, line, pie, or donut chart. Props: type, labels, values, title? | | Table | table | Data table. Props: headers, rows, caption? | | Stat | stat | Key metric display. Props: label, value, change?, trend? | | Progress | progress | Progress bar. Props: value, label?, max? |

Content (5)

| Component | Tag | Description | |-----------|-----|-------------| | Callout | callout | Alert / notice block. Props: type, title?. Accepts body content. | | Badge | badge | Inline label. Props: label, variant? | | Image | image | Inline image. Props: src, alt, width?, height? | | CodeBlock | code-block | Syntax-highlighted code. Props: language?, title?, code | | Link | link | Action link. Props: action, label, url? |


ComponentProps

Every component in the ComponentMap receives the same props interface:

interface ComponentProps {
  name: string                       // tag name, e.g. "button"
  props: Record<string, unknown>     // parsed attributes from the tag
  children?: React.ReactNode         // rendered child nodes (for body tags)
  onAction: ActionHandler            // fire an ActionEvent
  isStreaming: boolean               // whether the LLM is still streaming
}

onAction Event Handling

Interactive components fire ActionEvent objects through the onAction callback:

interface ActionEvent {
  type: 'button_click' | 'form_submit' | 'select_change' | 'link_click'
  action: string
  label?: string
  formName?: string
  formState?: Record<string, unknown>
  tagName: string
  params?: Record<string, unknown>
}
function handleAction(event: ActionEvent) {
  switch (event.type) {
    case 'button_click':
      if (event.action === 'continue') {
        // Send event.label as a new user message
      }
      break
    case 'form_submit':
      console.log(event.formName, event.formState)
      break
  }
}

<Renderer nodes={nodes} components={defaultComponents} onAction={handleAction} />

Custom Components

Pass custom components via the components prop — they merge on top of the 24 built-in defaults. You only need to pass overrides, not the full map:

import type { ComponentProps } from '@mdocui/react'

function MyCard({ props, children }: ComponentProps) {
  return (
    <div className="my-card">
      {props.title && <h3>{props.title as string}</h3>}
      {children}
    </div>
  )
}

// MyCard replaces the built-in card; all other 23 components still work
<Renderer nodes={nodes} components={{ card: MyCard }} />

Context Data

Pass app-level data to components without prop drilling. Useful for brand colors, account info, router, or any data the LLM doesn't generate:

<Renderer
  nodes={nodes}
  contextData={{ brandColor: '#E8845C', accountName: 'Acme Corp' }}
/>

Components access it via useMdocUI():

function MyChart({ props }: ComponentProps) {
  const { contextData } = useMdocUI()
  const color = (contextData?.brandColor as string) ?? 'currentColor'
  return <div style={{ color }}>{/* chart */}</div>
}

Context

useMdocUI() provides the renderer context from inside any component in the tree:

import { useMdocUI } from '@mdocui/react'

function MyComponent() {
  const { onAction, isStreaming, contextData } = useMdocUI()
  // ...
}

Must be used inside a <Renderer />.


License

See the root mdocui repository for license details.