Identical UI produced from a Zod schema via z.toJSONSchema(S). Zod is optional — use it when you want a single source of truth for rendering and validation.
/**
* The same shape as `./schema.ts`, built with Zod and converted to JSON Schema.
* Shown side-by-side in the Zod demo.
*/
import { z } from "zod";
import type { ObjectSchema } from "../src/render.tsx";
const S = z.object({
email: z
.email()
.meta({ uiName: "Email" })
.describe("Used for sign-in and account recovery."),
handle: z
.string()
.min(3)
.max(24)
.regex(/^[a-zA-Z0-9_]+$/)
.meta({ uiName: "Handle" })
.describe("3-24 letters, numbers, or underscores."),
homepage: z
.url()
.optional()
.meta({ uiName: "Homepage" })
.describe("Optional — link to your site or profile."),
plan: z
.enum(["free", "pro", "team"])
.default("pro")
.meta({ uiWidget: "select", uiName: "Plan" })
.describe("You can upgrade or downgrade any time."),
billing: z
.enum(["monthly", "yearly"])
.default("yearly")
.meta({ uiName: "Billing cycle" }),
seats: z
.int()
.min(1)
.max(50)
.default(5)
.meta({ uiWidget: "range", uiName: "Seats" })
.describe("Only relevant for team plans."),
notifications: z
.boolean()
.default(true)
.meta({ uiName: "Email me product updates" }),
topics: z
.array(z.enum(["frontend", "backend", "design", "ops"]))
.default(["frontend", "backend"])
.meta({ uiName: "Topics you care about" })
.describe("Pick at least one."),
channels: z
.array(z.enum(["email", "in-app", "sms", "push", "rss"]))
.default(["email", "in-app"])
.meta({ uiWidget: "select", uiName: "Notification channels" }),
profile: z
.object({
name: z.string().min(1).meta({ uiName: "Full name" }),
bio: z
.string()
.max(280)
.optional()
.meta({ uiWidget: "textarea", uiName: "Short bio" })
.describe("Up to 280 characters."),
})
.meta({ uiName: "Profile" }),
});
export const zodSchema = z.toJSONSchema(S) as ObjectSchema;
/**
* A realistic sign-up / profile schema, shared across most demos to keep
* them comparable: the only thing that changes between pages is the CSS.
*/
import type { ObjectSchema } from "../src/render.tsx";
export const schema: ObjectSchema = {
type: "object",
required: ["email", "handle", "plan", "topics"],
properties: {
email: {
type: "string",
format: "email",
uiName: "Email",
description: "Used for sign-in and account recovery.",
},
handle: {
type: "string",
minLength: 3,
maxLength: 24,
pattern: "^[a-zA-Z0-9_]+$",
uiName: "Handle",
description: "3-24 letters, numbers, or underscores.",
},
homepage: {
type: "string",
format: "uri",
uiName: "Homepage",
description: "Optional — link to your site or profile.",
},
plan: {
type: "string",
enum: ["free", "pro", "team"],
default: "pro",
uiWidget: "select",
uiName: "Plan",
description: "You can upgrade or downgrade any time.",
},
billing: {
type: "string",
enum: ["monthly", "yearly"],
default: "yearly",
uiName: "Billing cycle",
},
seats: {
type: "integer",
minimum: 1,
maximum: 50,
default: 5,
uiWidget: "range",
uiName: "Seats",
description: "Only relevant for team plans.",
},
notifications: {
type: "boolean",
default: true,
uiName: "Email me product updates",
},
topics: {
type: "array",
items: { type: "string", enum: ["frontend", "backend", "design", "ops"] },
default: ["frontend", "backend"],
uiName: "Topics you care about",
description: "Pick at least one.",
},
channels: {
type: "array",
items: {
type: "string",
enum: ["email", "in-app", "sms", "push", "rss"],
},
default: ["email", "in-app"],
uiWidget: "select",
uiName: "Notification channels",
},
profile: {
type: "object",
uiName: "Profile",
properties: {
name: {
type: "string",
minLength: 1,
uiName: "Full name",
},
bio: {
type: "string",
uiWidget: "textarea",
maxLength: 280,
uiName: "Short bio",
description: "Up to 280 characters.",
},
},
},
},
};