Skip to content

Repository files navigation

schema-components

GitHub npm version License: MIT GitHub Workflow Status API reference Storybook

React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents.

Define your data model once. Get presentational views, input fields, and editable forms — no manual wiring.

Install

npm install schema-components

Peer dependencies: zod@^4.0.0, react@^18.0.0 || ^19.0.0.

Zod version requirement

schema-components requires Zod 4. If you are on Zod 3, see the Zod 4 migration guide. If a Zod 3 schema is passed (detected via _def.typeName), a descriptive SchemaNormalisationError is raised pointing at the Zod 4 migration guide. Schemas from other Standard Schema libraries are not currently supported.

Quick start

import { z } from "zod";
import { SchemaComponent } from "schema-components/react/SchemaComponent";

const userSchema = z.object({
  name: z.string().min(1).meta({ description: "Full name" }),
  email: z.email().meta({ description: "Email address" }),
  role: z.enum(["admin", "editor", "viewer"]).meta({ description: "Role" }),
  active: z.boolean().meta({ description: "Active" }),
});

function UserCard() {
  const [user, setUser] = useState({
    name: "Ada Lovelace",
    email: "ada@example.com",
    role: "admin",
    active: true,
  });

  return (
    <SchemaComponent
      schema={userSchema}
      value={user}
      onChange={setUser}
    />
  );
}

Renders every field as an editable input. Add readOnly to the component for a read-only view:

<SchemaComponent schema={userSchema} value={user} readOnly />

How it works

flowchart LR
  Zod["Zod schema"] -- "z.toJSONSchema()" --> JS["JSON Schema"]
  JSONIn["JSON Schema input"] --> JS
  OAS["OpenAPI doc"] -- "extract schemas" --> JS
  JS -- "walker" --> React["React"]
Loading

One walker, one input format. The walker reads standard JSON Schema keywords (Draft 2020-12) — decoupled from Zod's internal API. z.toJSONSchema() is lossless: it preserves readOnly, writeOnly, custom .meta() properties, constraints, formats, and defaults.

z.fromJSONSchema() is used only for validation — converting JSON Schema / OpenAPI inputs back to Zod when validate is true and the original wasn't a Zod schema.

Spec support

Spec Support
JSON Schema Draft 04 / 06 / 07 / 2019-09 / 2020-12 All supported; older drafts normalised to Draft 2020-12
OpenAPI 2.0 (Swagger) Full document restructure to OpenAPI 3.1
OpenAPI 3.0.x nullable, discriminator, example normalised
OpenAPI 3.1.x Native; webhooks and components/pathItems resolved

See packages/core/README.md for the full keyword matrix and documented type-level fallbacks.

Examples

All input formats

<SchemaComponent> auto-detects the input format:

// Zod schema
<SchemaComponent schema={z.object({ name: z.string() })} value={data} />

// JSON Schema
<SchemaComponent
  schema={{ type: "object", properties: { name: { type: "string" } } }}
  value={data}
/>

// OpenAPI document + schemaRef
<SchemaComponent
  schema={openApiSpec}
  schemaRef="#/components/schemas/User"
  value={data}
/>

OpenAPI operations

Render API operations with type-safe field overrides:

import { ApiOperation } from "schema-components/openapi/components";

// Full operation — parameters, request body, responses
<ApiOperation schema={petStore} path="/pets" method="post" />

// Just the request body with type-safe fields
<ApiRequestBody
  schema={petStore}
  path="/pets"
  method="post"
  fields={{
    name: { description: "Pet name" },    // ✓ inferred from as const
  }}
/>

Theme adapters

Headless by default (plain HTML). Wrap with a theme adapter for styled components:

import { SchemaProvider } from "schema-components/react/SchemaComponent";
import { shadcnResolver } from "schema-components/themes/shadcn";

<SchemaProvider resolver={shadcnResolver}>
  <SchemaComponent schema={userSchema} value={user} onChange={setUser} />
</SchemaProvider>

Raw HTML (no React)

import { renderToHtml } from "schema-components/html/renderToHtml";

const html = renderToHtml(userSchema, {
  value: { name: "Ada Lovelace", email: "ada@example.com", role: "admin" },
  readOnly: true,
});

Server Components

import { SchemaView } from "schema-components/react/SchemaView";

export default async function Page() {
  const user = await getUser();
  return <SchemaView schema={userSchema} value={user} />;
}

Architecture

Every module is imported directly — no barrel files. Organised exports:

schema-components/core/*         # Walker, types, guards, errors, resolver
schema-components/react/*        # SchemaComponent, SchemaView, SchemaErrorBoundary, headless
schema-components/openapi/*      # Parser, ApiOperation, ApiParameters, etc.
schema-components/html/*         # renderToHtml, renderToHtmlChunks, h() builder, styles
schema-components/themes/*       # shadcn, MUI, custom adapters
schema-components/styles.css     # Default stylesheet for HTML output

Repository structure

Package Description
packages/core Published as schema-components on npm — the component library
packages/docs Storybook documentation site — not published

Development

pnpm install            # Install all workspace dependencies
pnpm build              # Build the core library
pnpm check              # Typecheck + lint + build
pnpm test               # Run unit tests
pnpm test:coverage      # Run tests with coverage
pnpm storybook          # Start Storybook dev server
pnpm build-storybook    # Build static Storybook site
pnpm typedoc            # Generate TypeDoc API reference

Documentation site

Published to GitHub Pages on every push to main:

Surface URL
API reference (TypeDoc) https://mearman.github.io/schema-components/
Storybook https://mearman.github.io/schema-components/storybook/

About

Render UI from Zod schemas, JSON Schema, and OpenAPI documents — define your data model once and get views, input fields, and editable forms. React components plus a framework-agnostic, headless raw-HTML renderer.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages