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.
npm install schema-componentsPeer dependencies: zod@^4.0.0, react@^18.0.0 || ^19.0.0.
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.
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 />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"]
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 |
|---|---|
| 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.
<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}
/>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
}}
/>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>import { renderToHtml } from "schema-components/html/renderToHtml";
const html = renderToHtml(userSchema, {
value: { name: "Ada Lovelace", email: "ada@example.com", role: "admin" },
readOnly: true,
});import { SchemaView } from "schema-components/react/SchemaView";
export default async function Page() {
const user = await getUser();
return <SchemaView schema={userSchema} value={user} />;
}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
| Package | Description |
|---|---|
packages/core |
Published as schema-components on npm — the component library |
packages/docs |
Storybook documentation site — not published |
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 referencePublished 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/ |