Raw output from the library with no stylesheet loaded. Proves the HTML is semantic on its own — form controls, required markers, groups, and nested objects all work with browser defaults.
<form method="post" action="#">
<div data-name="email" data-type="string" data-widget="email" data-required="true">
<label for="email">Email</label>
<input id="email" name="email" type="email" required="" aria-required="true" aria-describedby="email-hint"/>
<small id="email-hint">Used for sign-in and account recovery.</small>
</div>
<div data-name="handle" data-type="string" data-widget="text" data-required="true">
<label for="handle">Handle</label>
<input id="handle" name="handle" type="text" required="" aria-required="true" aria-describedby="handle-hint" minlength="3" maxlength="24" pattern="^[a-zA-Z0-9_]+$" title="^[a-zA-Z0-9_]+$"/>
<small id="handle-hint">3-24 letters, numbers, or underscores.</small>
</div>
<div data-name="homepage" data-type="string" data-widget="url">
<label for="homepage">Homepage</label>
<input id="homepage" name="homepage" type="url" aria-describedby="homepage-hint"/>
<small id="homepage-hint">Optional — link to your site or profile.</small>
</div>
<div data-name="plan" data-type="string" data-widget="select" data-required="true">
<label for="plan">Plan</label>
<select id="plan" name="plan" required="" aria-required="true" aria-describedby="plan-hint">
<option value="free">free</option>
<option value="pro" selected="">pro</option>
<option value="team">team</option>
</select>
<small id="plan-hint">You can upgrade or downgrade any time.</small>
</div>
<fieldset data-variant="group" data-name="billing" data-type="string" data-widget="radio">
<legend>Billing cycle</legend>
<label>
<input type="radio" name="billing" value="monthly"/>
<span>monthly</span>
</label>
<label>
<input type="radio" name="billing" value="yearly" checked=""/>
<span>yearly</span>
</label>
</fieldset>
<div data-name="seats" data-type="integer" data-widget="range">
<label for="seats">Seats</label>
<input id="seats" name="seats" type="range" aria-describedby="seats-hint" value="5" min="1" max="50" step="1"/>
<small id="seats-hint">Only relevant for team plans.</small>
</div>
<div data-name="notifications" data-type="boolean" data-widget="checkbox">
<input id="notifications" name="notifications" type="checkbox" checked=""/>
<label for="notifications">Email me product updates</label>
</div>
<fieldset data-variant="group" aria-required="true" aria-describedby="topics-hint" data-name="topics" data-type="array" data-widget="checkbox" data-required="true">
<legend>Topics you care about</legend>
<label>
<input type="checkbox" name="topics" value="frontend" checked=""/>
<span>frontend</span>
</label>
<label>
<input type="checkbox" name="topics" value="backend" checked=""/>
<span>backend</span>
</label>
<label>
<input type="checkbox" name="topics" value="design"/>
<span>design</span>
</label>
<label>
<input type="checkbox" name="topics" value="ops"/>
<span>ops</span>
</label>
<small id="topics-hint">Pick at least one.</small>
</fieldset>
<div data-name="channels" data-type="array" data-widget="select">
<label for="channels">Notification channels</label>
<select id="channels" name="channels" multiple="">
<option value="email" selected="">email</option>
<option value="in-app" selected="">in-app</option>
<option value="sms">sms</option>
<option value="push">push</option>
<option value="rss">rss</option>
</select>
</div>
<fieldset data-variant="object" data-name="profile" data-type="object" data-widget="object">
<legend>Profile</legend>
<div data-name="profile.name" data-type="string" data-widget="text">
<label for="profile-name">Full name</label>
<input id="profile-name" name="profile.name" type="text" minlength="1"/>
</div>
<div data-name="profile.bio" data-type="string" data-widget="textarea">
<label for="profile-bio">Short bio</label>
<textarea id="profile-bio" name="profile.bio" aria-describedby="profile-bio-hint" maxlength="280">
</textarea>
<small id="profile-bio-hint">Up to 280 characters.</small>
</div>
</fieldset> <button type="submit">Create account</button>
</form>/**
* 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.",
},
},
},
},
};