docs
TypeScript SDKReference

Component Registry

API reference for registerComponent, ComponentRenderer, and the built-in components

Render data-driven component specs — a component name plus props — with Zod-validated safety. @urun-sh/react ships a small client-side registry plus five built-in components. Your app decides where specs come from: a named data stream (read with useStreamMessages), a doc field, or any app-defined envelope. The registry only resolves and validates — it never renders anything you didn't register.

Public API at a glance

NameDescription
registerComponent(name, component, schema)Register a React component for spec-driven rendering
ComponentRendererResolve a spec by name, validate props, render (or fall back)
ProgressCardStep progress with optional label
StatusBadgeAgent state indicator badge
TextStreamStreaming text output with cursor
ImageFrameRendered image with optional caption
MetricsPanelKey-value metrics grid

registerComponent()

function registerComponent(
  name: string,
  component: React.ComponentType<any>,
  schema: ZodSchema
): void

Register a React component under a name. Once registered, any spec carrying that name renders through ComponentRenderer with Zod-validated props.

ParameterTypeDefaultDescription
namestringrequiredComponent name (must match the spec's component field)
componentReact.ComponentTyperequiredReact component to render
schemaZodSchemarequiredZod schema for prop validation

Register components before rendering specs (module scope is the usual place). Invalid props render a fallback error state, not a crash.

import { registerComponent } from '@urun-sh/react'
import { z } from 'zod'

const MyCardProps = z.object({ title: z.string(), body: z.string() })

function MyCard({ title, body }: z.infer<typeof MyCardProps>) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <p>{body}</p>
    </div>
  )
}

registerComponent('MyCard', MyCard, MyCardProps)

ComponentRenderer

<ComponentRenderer name={spec.component} props={spec.props} fallback={<Skeleton />} />

Resolves name in the registry, validates props with the registered Zod schema, and renders the component. Unknown names or invalid props render fallback (or a small inline error state) and log a console warning — never a crash.

PropTypeDefaultDescription
namestringrequiredRegistered component name (the spec's component field)
propsunknownrequiredThe spec's props, validated against the registered schema
fallbackReactNodeRendered when resolution/validation fails

A typical spec source is a named data stream the runtime emits on — the spec shape (component + props) is your app's convention, not a platform protocol:

import { ComponentRenderer, useStreamMessages } from '@urun-sh/react'

function StatusPanel({ session }) {
  const specs = useStreamMessages(session, 'ui')   // ctx.stream("ui").emit({component, props}) on Python
  return specs.map(({ at, payload }) => {
    const spec = payload as { component: string; props: unknown }
    return <ComponentRenderer key={at} name={spec.component} props={spec.props} />
  })
}
# Python runtime — emit specs on a named data stream
ctx.stream("ui").emit({
    "component": "ProgressCard",
    "props": {"step": 42, "total": 100, "label": "Generating frames"},
})

Import the package CSS once (import '@urun-sh/react/styles.css') when using the styled built-ins.


Built-in components

Five components ship with @urun-sh/react, each with a styled variant, a headless hook, and a Zod schema export (ProgressCardSchema, …). Register the ones you drive from spec data — registerComponent('ProgressCard', ProgressCard, ProgressCardSchema) — or use them directly as plain React components.

ComponentHookPurpose
ProgressCarduseProgressCardStep progress with an optional label
StatusBadgeuseStatusBadgeCompact status badge
TextStreamuseTextStreamToken-by-token streaming text
ImageFrameuseImageFrameA rendered image with optional caption
MetricsPaneluseMetricsPanelKey/value metrics grid

ProgressCard

const ProgressCardSchema = z.object({
  step: z.number().min(0),
  total: z.number().min(1),
  label: z.string().optional(),
  variant: z.enum(['default', 'success', 'error']).default('default'),
})
PropTypeDefaultDescription
stepnumberrequiredCurrent step count
totalnumberrequiredTotal step count
labelstringOptional label above the progress bar
variant'default' | 'success' | 'error''default'Color variant

StatusBadge

const StatusBadgeSchema = z.object({
  state: z.enum(['thinking', 'generating', 'idle', 'error']),
  message: z.string().optional(),
})
PropTypeDefaultDescription
state'thinking' | 'generating' | 'idle' | 'error'requiredCurrent state
messagestringOptional override message

TextStream

const TextStreamSchema = z.object({
  text: z.string(),
  streaming: z.boolean().default(false),
})
PropTypeDefaultDescription
textstringrequiredCurrent accumulated text
streamingbooleanfalseWhether to show the streaming cursor

ImageFrame

const ImageFrameSchema = z.object({
  src: z.string().url(),
  alt: z.string().optional(),
  caption: z.string().optional(),
})
PropTypeDefaultDescription
srcstringrequiredImage URL
altstringAlt text for accessibility
captionstringCaption displayed below the image

MetricsPanel

const MetricsPanelSchema = z.object({
  metrics: z.array(z.object({
    label: z.string(),
    value: z.union([z.string(), z.number()]),
    unit: z.string().optional(),
  })),
})
PropTypeDefaultDescription
metricsArray<{ label, value, unit? }>requiredMetric rows (unit is an optional suffix, e.g. "fps", "ms")

Validation is safe by default

Registered components are validated at render time. If a spec's props fail the Zod schema, the SDK renders a fallback error state instead of throwing — spec producers can push arbitrary data and the client stays resilient.

On this page