<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hamza Hamidi</title>
    <description>The latest articles on DEV Community by Hamza Hamidi (@hamzahamidi).</description>
    <link>https://dev.to/hamzahamidi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F138712%2F05b76b9e-041e-4d35-a6e1-2e6e96307f63.jpeg</url>
      <title>DEV Community: Hamza Hamidi</title>
      <link>https://dev.to/hamzahamidi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamzahamidi"/>
    <language>en</language>
    <item>
      <title>AJSF on Angular 22, and the way up from 0.8.0</title>
      <dc:creator>Hamza Hamidi</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:44:53 +0000</pubDate>
      <link>https://dev.to/hamzahamidi/ajsf-on-angular-22-and-the-way-up-from-080-244j</link>
      <guid>https://dev.to/hamzahamidi/ajsf-on-angular-22-and-the-way-up-from-080-244j</guid>
      <description>&lt;p&gt;AJSF turns a JSON Schema into an Angular form. &lt;code&gt;@ajsf/core&lt;/code&gt; renders plain HTML, and five framework packages render it with a UI library: &lt;code&gt;@ajsf/material&lt;/code&gt;, &lt;code&gt;@ajsf/primeng&lt;/code&gt;, &lt;code&gt;@ajsf/bootstrap3&lt;/code&gt;, &lt;code&gt;@ajsf/bootstrap4&lt;/code&gt; and &lt;code&gt;@ajsf/bootstrap5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the week of 15 to 21 September 2026, &lt;code&gt;@ajsf/core&lt;/code&gt; had &lt;a href="https://api.npmjs.org/downloads/point/2026-09-15:2026-09-21/@ajsf/core" rel="noopener noreferrer"&gt;2,932 npm downloads&lt;/a&gt;, over five days of data, since npm &lt;a href="https://api.npmjs.org/downloads/range/2026-09-15:2026-09-21/@ajsf/core" rel="noopener noreferrer"&gt;reports none&lt;/a&gt; for 15 and 17 September. As npm's &lt;a href="https://www.npmjs.com/package/@ajsf/core?activeTab=versions" rel="noopener noreferrer"&gt;versions tab&lt;/a&gt; showed them on 24 September, 2,289 of those were version &lt;code&gt;0.8.0&lt;/code&gt;, from August 2022, and 216 in total were &lt;code&gt;14.0.0&lt;/code&gt; or later. So this note is mostly for people still on &lt;code&gt;0.8.0&lt;/code&gt;: which version to move to, what changes on the way, and what a new Angular 22 app needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why so many installs are still on &lt;code&gt;0.8.0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;No AJSF release shipped between &lt;a href="https://www.npmjs.com/package/@ajsf/core/v/0.8.0" rel="noopener noreferrer"&gt;&lt;code&gt;0.8.0&lt;/code&gt;&lt;/a&gt;, published on 27 August 2022, and &lt;a href="https://www.npmjs.com/package/@ajsf/core/v/14.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;14.0.0&lt;/code&gt;&lt;/a&gt;, published on 15 August 2026.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@ajsf/core@0.8.0&lt;/code&gt; declares &lt;code&gt;@angular/core &amp;gt;=14.0.0&lt;/code&gt;, an open range, so npm installs it next to any later Angular without a warning. &lt;code&gt;@ajsf/material@0.8.0&lt;/code&gt; also peers on &lt;code&gt;@angular/flex-layout&lt;/code&gt;, whose &lt;a href="https://www.npmjs.com/package/@angular/flex-layout?activeTab=versions" rel="noopener noreferrer"&gt;matching releases&lt;/a&gt; require Angular 14, so on Angular 15 or later npm stops with &lt;code&gt;ERESOLVE&lt;/code&gt; unless it runs with &lt;code&gt;--legacy-peer-deps&lt;/code&gt;. Neither means the combination was ever built or tested.&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;14.0.0&lt;/code&gt;, the Angular peer ranges are bounded, so npm reports a resolution error instead of installing a combination nobody built. AJSF then followed Angular one major at a time, up to &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;22.0.0&lt;/code&gt;&lt;/a&gt; on 12 September 2026. The current release is &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.3.1" rel="noopener noreferrer"&gt;&lt;code&gt;22.3.1&lt;/code&gt;&lt;/a&gt;, published on 24 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the version that matches your Angular
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;14.0.0&lt;/code&gt; onward, the AJSF major equals the Angular major it targets, as Angular Material does. Minor and patch numbers are not tied to Angular, so a major can have several releases, such as &lt;code&gt;22.0.0&lt;/code&gt;, &lt;code&gt;22.1.0&lt;/code&gt;, &lt;code&gt;22.2.0&lt;/code&gt;, &lt;code&gt;22.3.0&lt;/code&gt; and &lt;code&gt;22.3.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@ajsf/core?activeTab=versions" rel="noopener noreferrer"&gt;majors that exist&lt;/a&gt; are &lt;code&gt;14&lt;/code&gt;, &lt;code&gt;16&lt;/code&gt;, &lt;code&gt;17&lt;/code&gt;, &lt;code&gt;18&lt;/code&gt;, &lt;code&gt;19&lt;/code&gt;, &lt;code&gt;20&lt;/code&gt;, &lt;code&gt;21&lt;/code&gt; and &lt;code&gt;22&lt;/code&gt;. There is no release for Angular 15: &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v16.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;16.0.0&lt;/code&gt;&lt;/a&gt; crossed 15 on the way from 14 to 16.&lt;/p&gt;

&lt;p&gt;A plain &lt;code&gt;npm install @ajsf/material&lt;/code&gt; takes the newest major, which fails to resolve on an older Angular, so name the major that matches the Angular you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ajsf/core@17 @ajsf/material@17   &lt;span class="c"&gt;# for Angular 17&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@ajsf/*@17&lt;/code&gt; installs &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v17.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;17.1.0&lt;/code&gt;&lt;/a&gt;, which has none of the fixes listed below for 18 and later. Fixes ship only in the newest release, with no backports to earlier majors, &lt;code&gt;0.8.0&lt;/code&gt; or &lt;code&gt;angular6-json-schema-form&lt;/code&gt;, so an app on an older Angular gets them by moving to Angular 22.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;14.0.0&lt;/code&gt; is not a rewrite: it targets the same Angular 14 as &lt;code&gt;0.8.0&lt;/code&gt;, and the version jump is the Angular-aligned numbering starting.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;@ajsf&lt;/code&gt; release since &lt;code&gt;14.0.0&lt;/code&gt; is published from the repository's &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/.github/workflows/release.yml" rel="noopener noreferrer"&gt;release workflow&lt;/a&gt; with an npm provenance attestation that records the repository and workflow, and &lt;code&gt;npm audit signatures&lt;/code&gt; verifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade from &lt;code&gt;0.8.0&lt;/code&gt;, one major at a time
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Remove flex-layout.&lt;/strong&gt; &lt;code&gt;@ajsf/material&lt;/code&gt; no longer depends on &lt;code&gt;@angular/flex-layout&lt;/code&gt;, which is deprecated and has &lt;a href="https://www.npmjs.com/package/@angular/flex-layout?activeTab=versions" rel="noopener noreferrer"&gt;no Angular 16 release&lt;/a&gt;. Unless something else uses it, run &lt;code&gt;npm uninstall @angular/flex-layout --legacy-peer-deps&lt;/code&gt; first: while its &lt;code&gt;14.0.0&lt;/code&gt; beta sits next to a newer Angular, the next install stops with &lt;code&gt;ERESOLVE&lt;/code&gt;. The &lt;code&gt;fxFlex&lt;/code&gt;, &lt;code&gt;fxFlexOrder&lt;/code&gt;, &lt;code&gt;fxFlexOffset&lt;/code&gt;, &lt;code&gt;fxFlexAlign&lt;/code&gt;, &lt;code&gt;fxLayoutAlign&lt;/code&gt; and &lt;code&gt;fxLayoutGap&lt;/code&gt; options and the &lt;code&gt;flex&lt;/code&gt; and &lt;code&gt;section&lt;/code&gt; layout types still apply, as native CSS, for values written with a unit such as &lt;code&gt;30%&lt;/code&gt; or &lt;code&gt;10px&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match your Angular.&lt;/strong&gt; Install the &lt;code&gt;@ajsf&lt;/code&gt; major for the Angular you run, for every &lt;code&gt;@ajsf&lt;/code&gt; package you use, as above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move Angular and AJSF up one major together&lt;/strong&gt; with one &lt;code&gt;ng update&lt;/code&gt; that names every &lt;code&gt;@ajsf&lt;/code&gt; package you use, as &lt;a href="https://angular.dev/update-guide" rel="noopener noreferrer"&gt;Angular's update guide&lt;/a&gt; describes, for example &lt;code&gt;ng update @angular/core@18 @angular/cli@18 @angular/material@18 @ajsf/core@18 @ajsf/material@18&lt;/code&gt;. Without the &lt;code&gt;@ajsf&lt;/code&gt; packages, &lt;code&gt;ng update&lt;/code&gt; stops with &lt;code&gt;Incompatible peer dependencies found&lt;/code&gt;, because &lt;code&gt;@ajsf/material@17&lt;/code&gt; peers on &lt;code&gt;@angular/material ^17.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your forms&lt;/strong&gt; against the list below, then repeat step 3 up to Angular 22.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What a form can notice between &lt;code&gt;0.8.0&lt;/code&gt; and &lt;code&gt;22&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;20.0.0&lt;/code&gt;, &lt;code&gt;21.0.0&lt;/code&gt; and &lt;code&gt;22.0.0&lt;/code&gt; releases changed no export, selector or input, while minors inside those majors changed Bootstrap markup. The changes below, from 16 to 22, can affect what a form shows, validates or submits, or what your project needs to build it. Upgrade notes for 19 to 22 are in the &lt;a href="https://github.com/hamzahamidi/ajsf/tree/main/docs/release-notes" rel="noopener noreferrer"&gt;release notes directory&lt;/a&gt;; for 14 to 18, read each release's page in the &lt;a href="https://github.com/hamzahamidi/ajsf/releases" rel="noopener noreferrer"&gt;GitHub releases&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v16.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;16.0.0&lt;/code&gt;&lt;/a&gt;, Material&lt;/strong&gt;: the widgets use Angular Material's MDC components (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/367" rel="noopener noreferrer"&gt;#367&lt;/a&gt;). A field without an &lt;code&gt;appearance&lt;/code&gt; option renders as &lt;code&gt;fill&lt;/code&gt; instead of &lt;code&gt;standard&lt;/code&gt;. CSS written against &lt;code&gt;.mat-form-field-*&lt;/code&gt; classes needs the &lt;code&gt;.mat-mdc-*&lt;/code&gt; names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v17.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;17.1.0&lt;/code&gt;&lt;/a&gt;, messages&lt;/strong&gt;: validator and locale messages are corrected (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/374" rel="noopener noreferrer"&gt;#374&lt;/a&gt;), for example the message for a decimal &lt;code&gt;multipleOf&lt;/code&gt; step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;18.x, field validators&lt;/strong&gt;: duplicates under &lt;code&gt;uniqueItems&lt;/code&gt; (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/379" rel="noopener noreferrer"&gt;#379&lt;/a&gt;), exclusive bounds (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/418" rel="noopener noreferrer"&gt;#418&lt;/a&gt;) and an emptied array under &lt;code&gt;minItems&lt;/code&gt; (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/396" rel="noopener noreferrer"&gt;#396&lt;/a&gt;) now mark the field, and &lt;code&gt;dependencies&lt;/code&gt; is checked per field (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/376" rel="noopener noreferrer"&gt;#376&lt;/a&gt;). Decimal &lt;code&gt;multipleOf&lt;/code&gt; steps and emoji in length limits stop showing false errors (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/396" rel="noopener noreferrer"&gt;#396&lt;/a&gt;). &lt;code&gt;isValid&lt;/code&gt; and submit come from ajv, which already judged all of these cases correctly, so what changes is the message on the field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v19.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;19.0.0&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;format&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;0.8.0&lt;/code&gt; already checked &lt;code&gt;format&lt;/code&gt; with ajv 6. From 19.0.0, ajv 8 with ajv-formats is stricter, so &lt;code&gt;2021-02-30&lt;/code&gt; no longer passes &lt;code&gt;format: date&lt;/code&gt; and &lt;code&gt;a@b&lt;/code&gt; no longer passes &lt;code&gt;format: email&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;19.0.0&lt;/code&gt;, drafts&lt;/strong&gt;: the draft is read from &lt;code&gt;$schema&lt;/code&gt;, and a schema without it is read as draft 7, though draft 4 keywords are still converted, so a draft 4 schema without &lt;code&gt;$schema&lt;/code&gt; keeps working. Draft 4 is the oldest draft supported: a schema declaring draft 3 still renders, with its property-level keywords handled as in the next bullet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;19.0.0&lt;/code&gt;, property-level &lt;code&gt;required&lt;/code&gt;&lt;/strong&gt;: a property-level &lt;code&gt;required: true&lt;/code&gt;, &lt;code&gt;optional&lt;/code&gt; or &lt;code&gt;requires&lt;/code&gt; is dropped with a console warning naming the properties, so that field is no longer required. Move each &lt;code&gt;required: true&lt;/code&gt; name into the parent object's &lt;code&gt;required&lt;/code&gt; array, list there every property without &lt;code&gt;optional: true&lt;/code&gt;, and turn &lt;code&gt;requires&lt;/code&gt; into a &lt;code&gt;dependencies&lt;/code&gt; entry. This is the change most likely to need a schema edit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;19.0.0&lt;/code&gt;, references and selects&lt;/strong&gt;: &lt;code&gt;$ref&lt;/code&gt; against a schema's own &lt;code&gt;id&lt;/code&gt; resolves. A select over a numeric enum submits numbers instead of strings, and clearing an optional select with its None option no longer makes the form invalid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;19.0.0&lt;/code&gt;, counts and buttons&lt;/strong&gt;: &lt;code&gt;minProperties&lt;/code&gt; and &lt;code&gt;maxProperties&lt;/code&gt; count only entered values: undefined, null and the empty string do not count, &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; do. A layout-declared submit button honours &lt;code&gt;disableInvalidSubmit&lt;/code&gt;, and fixed tuple positions show no remove button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;20.0.0&lt;/code&gt; and &lt;code&gt;22.0.0&lt;/code&gt;, toolchain&lt;/strong&gt;: moving to 20 needs Node &lt;code&gt;^20.19.0 || ^22.12.0 || &amp;gt;=24.0.0&lt;/code&gt; (&lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/docs/release-notes/20.md" rel="noopener noreferrer"&gt;20 notes&lt;/a&gt;). At 22, TypeScript must be &lt;code&gt;&amp;gt;=6.0 &amp;lt;6.1&lt;/code&gt; and Node &lt;code&gt;^22.22.3 || ^24.15.0 || &amp;gt;=26.0.0&lt;/code&gt; (&lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/docs/release-notes/22.md" rel="noopener noreferrer"&gt;22 notes&lt;/a&gt;). TypeScript 6 turns &lt;code&gt;strict&lt;/code&gt; on when &lt;code&gt;tsconfig.json&lt;/code&gt; does not set it, so set &lt;code&gt;"strict": false&lt;/code&gt; or fix the new errors, and it deprecates &lt;code&gt;baseUrl&lt;/code&gt; with &lt;code&gt;TS5101&lt;/code&gt;: without &lt;code&gt;baseUrl&lt;/code&gt;, each &lt;code&gt;paths&lt;/code&gt; target needs a leading &lt;code&gt;./&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v20.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;20.1.0&lt;/code&gt;&lt;/a&gt;, Bootstrap 4 classes&lt;/strong&gt;: &lt;code&gt;@ajsf/bootstrap4&lt;/code&gt; emits Bootstrap 4 class names instead of Bootstrap 3 ones, for example &lt;code&gt;is-invalid&lt;/code&gt; instead of &lt;code&gt;has-error&lt;/code&gt;. The &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/projects/ajsf-bootstrap4/README.md" rel="noopener noreferrer"&gt;package README&lt;/a&gt; tables every replacement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;22.1.0&lt;/code&gt;&lt;/a&gt;, Bootstrap 4 and 5 checkboxes and radios&lt;/strong&gt;: the input and its label render as siblings instead of the label wrapping the input, except Bootstrap 4 toggle buttons, which stay nested. CSS selecting &lt;code&gt;label &amp;gt; input&lt;/code&gt; no longer matches.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-check"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-check-input"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"control1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-check-label"&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"control1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Accept&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet matches Bootstrap 4 and the Bootstrap 5 radios and checkbox lists. For a single Bootstrap 5 checkbox, the input and label share a &lt;code&gt;div&lt;/code&gt; nested inside &lt;code&gt;.form-check&lt;/code&gt;, so a descendant selector matches every case.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.2.0" rel="noopener noreferrer"&gt;&lt;code&gt;22.2.0&lt;/code&gt;&lt;/a&gt;, Bootstrap arrays&lt;/strong&gt;: an array title renders once, on the &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt;, in all three Bootstrap packages. In Bootstrap 4 and 5, every field sits in a wrapper row that carries &lt;code&gt;is-invalid&lt;/code&gt; and, for an array item, holds the remove button beside the field, so a selector such as &lt;code&gt;.input-group.is-invalid&lt;/code&gt; no longer matches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.3.0" rel="noopener noreferrer"&gt;&lt;code&gt;22.3.0&lt;/code&gt;&lt;/a&gt;, Bootstrap messages&lt;/strong&gt;: in all three Bootstrap packages, a field's validation message appears under it once the field is touched or changed (&lt;a href="https://github.com/hamzahamidi/ajsf/pull/524" rel="noopener noreferrer"&gt;#524&lt;/a&gt;). Bootstrap 5 puts &lt;code&gt;is-invalid&lt;/code&gt; on the wrapper around the field, not on the input, and the red border on a touched invalid input comes from AJSF's own &lt;code&gt;.ng-invalid.ng-touched&lt;/code&gt; rule.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New packages&lt;/strong&gt;: &lt;code&gt;@ajsf/bootstrap5&lt;/code&gt; is released from &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v18.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;18.0.0&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;@ajsf/primeng&lt;/code&gt; from &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v19.2.0" rel="noopener noreferrer"&gt;&lt;code&gt;19.2.0&lt;/code&gt;&lt;/a&gt;. Neither existed in &lt;code&gt;0.8.0&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before each release, 74 recorded schemas are rendered in every framework, &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/docs/release-notes/20.md" rel="noopener noreferrer"&gt;444 cases&lt;/a&gt; in all, and their control count and validity are compared with a recorded baseline. That baseline holds no class names, so the Bootstrap markup changes above do not show in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A new Angular 22 app
&lt;/h2&gt;

&lt;p&gt;I built and ran this setup in Chrome on 24 September 2026 with Angular CLI 22.2.0, Angular Material 22.2.0 and &lt;code&gt;@ajsf&lt;/code&gt; 22.3.1. The CLI scaffolds a standalone, zoneless app, and this Material setup needs neither &lt;code&gt;zone.js&lt;/code&gt; nor &lt;code&gt;@angular/animations&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @angular/cli@22 new ajsf-demo &lt;span class="nt"&gt;--defaults&lt;/span&gt; &lt;span class="nt"&gt;--style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;css
&lt;span class="nb"&gt;cd &lt;/span&gt;ajsf-demo
npx ng add @angular/material
npm &lt;span class="nb"&gt;install&lt;/span&gt; @ajsf/material
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ng add @angular/material&lt;/code&gt; asks whether to proceed and which palette, and its defaults give the setup below: it installs Material and the CDK, writes a theme and links the fonts. &lt;code&gt;@ajsf/material&lt;/code&gt; brings &lt;code&gt;@ajsf/core&lt;/code&gt; with it; an app that already lists &lt;code&gt;@ajsf/core&lt;/code&gt; names it in the install too, so both stay on the same version.&lt;/p&gt;

&lt;p&gt;Nothing goes in &lt;code&gt;app.config.ts&lt;/code&gt;: &lt;code&gt;MaterialDesignFrameworkModule&lt;/code&gt; goes in the component's &lt;code&gt;imports&lt;/code&gt; and provides what the form needs. An app that still bootstraps an &lt;code&gt;NgModule&lt;/code&gt; adds it to that module's &lt;code&gt;imports&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;src/app/app.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JsonPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MaterialDesignFrameworkModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ajsf/material&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MaterialDesignFrameworkModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JsonPipe&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;integer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Plan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;team&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;submitted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;src/app/app.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;json-schema-form&lt;/span&gt;
  &lt;span class="na"&gt;[schema]=&lt;/span&gt;&lt;span class="s"&gt;"schema"&lt;/span&gt;
  &lt;span class="na"&gt;framework=&lt;/span&gt;&lt;span class="s"&gt;"material-design"&lt;/span&gt;
  &lt;span class="na"&gt;(onSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"onSubmit($event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/json-schema-form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;{{ submitted() | json }}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser showed Name, Age and Plan as Material form fields and a select, with the required asterisk on Name and Submit disabled. Touching Name and leaving it empty showed &lt;code&gt;This field is required.&lt;/code&gt;, and typing 16 in Age and leaving the field showed &lt;code&gt;Must be 18 or more&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With valid values, Submit called the handler with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada Lovelace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pro"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Age arrived as a number.&lt;/p&gt;

&lt;p&gt;The production bundle was 1.40 MB, and a new project fails the build above &lt;a href="https://github.com/angular/angular-cli/blob/v22.2.0/packages/schematics/angular/application/index.ts" rel="noopener noreferrer"&gt;1 MB&lt;/a&gt;. Raise the &lt;code&gt;initial&lt;/code&gt; entry under &lt;code&gt;budgets&lt;/code&gt; in the &lt;code&gt;production&lt;/code&gt; configuration of &lt;code&gt;angular.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"initial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maximumWarning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2MB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maximumError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.5MB"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build also warns that &lt;code&gt;ajv&lt;/code&gt; and &lt;code&gt;ajv-formats&lt;/code&gt; are CommonJS. Listing them in &lt;code&gt;allowedCommonJsDependencies&lt;/code&gt; in the build &lt;code&gt;options&lt;/code&gt; silences the warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"allowedCommonJsDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ajv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ajv-formats"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example leaves &lt;code&gt;loadExternalAssets&lt;/code&gt; off. If your templates set it, bind it as &lt;code&gt;[loadExternalAssets]="true"&lt;/code&gt;: a project created by &lt;code&gt;ng new&lt;/code&gt; rejects the plain attribute &lt;code&gt;loadExternalAssets="true"&lt;/code&gt; with &lt;code&gt;TS2322&lt;/code&gt;. It loads CDN assets for trying the library out (for Material only the Roboto and Material Icons fonts, not a theme), so a production app loads its own and leaves the input off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching framework
&lt;/h2&gt;

&lt;p&gt;Four things change for Bootstrap 5: the package, the module import, the &lt;code&gt;framework&lt;/code&gt; value and the stylesheet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ajsf/bootstrap5 bootstrap@5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-import { MaterialDesignFrameworkModule } from '@ajsf/material';
&lt;/span&gt;&lt;span class="gi"&gt;+import { Bootstrap5FrameworkModule } from '@ajsf/bootstrap5';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  imports: [MaterialDesignFrameworkModule, JsonPipe],
&lt;/span&gt;&lt;span class="gi"&gt;+  imports: [Bootstrap5FrameworkModule, JsonPipe],
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  framework="material-design"
&lt;/span&gt;&lt;span class="gi"&gt;+  framework="bootstrap-5"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;angular.json&lt;/code&gt;, replace &lt;code&gt;src/material-theme.scss&lt;/code&gt; in the &lt;code&gt;styles&lt;/code&gt; array with the Bootstrap stylesheet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"styles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/bootstrap/dist/css/bootstrap.min.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"src/styles.css"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Bootstrap 5 bundle is 827 kB, which passes the default 1 MB error budget, though the build still warns above &lt;a href="https://github.com/angular/angular-cli/blob/v22.2.0/packages/schematics/angular/application/index.ts" rel="noopener noreferrer"&gt;500 kB&lt;/a&gt;. Bootstrap 4 works the same way with &lt;code&gt;@ajsf/bootstrap4&lt;/code&gt;, &lt;code&gt;bootstrap@4&lt;/code&gt;, &lt;code&gt;Bootstrap4FrameworkModule&lt;/code&gt; and &lt;code&gt;framework="bootstrap-4"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;PrimeNG works the same way with &lt;code&gt;PrimengFrameworkModule&lt;/code&gt; and &lt;code&gt;framework="primeng"&lt;/code&gt;, plus &lt;code&gt;providePrimeNG&lt;/code&gt; with a theme preset in &lt;code&gt;app.config.ts&lt;/code&gt;, as the &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/projects/ajsf-primeng/README.md" rel="noopener noreferrer"&gt;&lt;code&gt;@ajsf/primeng&lt;/code&gt; README&lt;/a&gt; shows. Its bundle is &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/projects/ajsf-primeng/README.md" rel="noopener noreferrer"&gt;1.55 MB&lt;/a&gt;, so it needs the raised budget too.&lt;/p&gt;

&lt;p&gt;An app can use several framework packages at once: import each framework module in the component that renders its form, and give that form the matching &lt;code&gt;framework&lt;/code&gt; value. The &lt;a href="https://hamidihamza.com/ajsf/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; also switches between them at runtime. The &lt;code&gt;framework&lt;/code&gt; values are &lt;code&gt;material-design&lt;/code&gt;, &lt;code&gt;primeng&lt;/code&gt;, &lt;code&gt;bootstrap-3&lt;/code&gt;, &lt;code&gt;bootstrap-4&lt;/code&gt;, &lt;code&gt;bootstrap-5&lt;/code&gt; and &lt;code&gt;no-framework&lt;/code&gt;, the default, which &lt;code&gt;JsonSchemaFormModule&lt;/code&gt; from &lt;code&gt;@ajsf/core&lt;/code&gt; renders as plain HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which JSON Schema it understands
&lt;/h2&gt;

&lt;p&gt;AJSF understands draft 4, draft 6 and draft 7. Draft 4 keywords are converted whether or not a schema declares &lt;code&gt;$schema&lt;/code&gt;, so a draft 4 schema without it keeps working.&lt;/p&gt;

&lt;p&gt;A declared draft 4, 6 or 7 &lt;code&gt;$schema&lt;/code&gt; is recognised over &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt; and with or without the trailing &lt;code&gt;#&lt;/code&gt;, from &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.3.1" rel="noopener noreferrer"&gt;&lt;code&gt;22.3.1&lt;/code&gt;&lt;/a&gt;. 2019-09 and 2020-12 are not supported: a schema declaring either fails to compile, and the form does not render.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; validate, so a conditionally required field keeps Submit disabled. The layout is built once, though, so that field shows no required marker and no message, even after it is touched. &lt;code&gt;readOnly&lt;/code&gt; and &lt;code&gt;writeOnly&lt;/code&gt; are accepted and have no effect.&lt;/p&gt;

&lt;p&gt;The playground has &lt;a href="https://hamidihamza.com/ajsf/llms.txt" rel="noopener noreferrer"&gt;71 example schemas&lt;/a&gt;, each viewable with Material, PrimeNG, Bootstrap 3, 4 or 5, or plain HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't there yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Support for 2019-09 and 2020-12, and a layout that follows &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt;, both described above.&lt;/li&gt;
&lt;li&gt;A single validator. JSON Schema is validated twice today, by &lt;code&gt;ajv&lt;/code&gt; for &lt;code&gt;isValid&lt;/code&gt; and &lt;code&gt;validationErrors&lt;/code&gt; and by hand-written per-field validators, and the two can disagree.&lt;/li&gt;
&lt;li&gt;A daisyUI package is next on the &lt;a href="https://github.com/hamzahamidi/ajsf/blob/main/docs/roadmap.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;. ng-zorro and Ionic wait until users ask for them in an &lt;a href="https://github.com/hamzahamidi/ajsf/issues" rel="noopener noreferrer"&gt;issue&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  History and links
&lt;/h2&gt;

&lt;p&gt;AJSF has continued &lt;a href="https://github.com/dschnelldavis/angular2-json-schema-form" rel="noopener noreferrer"&gt;dschnelldavis/angular2-json-schema-form&lt;/a&gt; in its own repository since &lt;a href="https://github.com/hamzahamidi/ajsf/commit/b148449f07b5b475a993d24fc443d0c7a62555ab" rel="noopener noreferrer"&gt;July 2018&lt;/a&gt;. It first shipped as the single package &lt;code&gt;angular6-json-schema-form&lt;/code&gt;, whose last release is &lt;a href="https://www.npmjs.com/package/angular6-json-schema-form/v/8.0.0" rel="noopener noreferrer"&gt;&lt;code&gt;8.0.0&lt;/code&gt;&lt;/a&gt;, and later moved to the &lt;code&gt;@ajsf&lt;/code&gt; scope. What it led to is in &lt;a href="https://hamidihamza.com/notes/texaryn-json-schema-forms/" rel="noopener noreferrer"&gt;my note on Texaryn&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hamidihamza.com/ajsf/" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hamidihamza.com/ajsf/api/" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hamidihamza.com/ajsf/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt; for AI assistants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hamzahamidi/ajsf#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hamzahamidi/ajsf/releases" rel="noopener noreferrer"&gt;Release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hamzahamidi/ajsf/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;. Security reports go through GitHub &lt;a href="https://github.com/hamzahamidi/ajsf/security/policy" rel="noopener noreferrer"&gt;private vulnerability reporting&lt;/a&gt;, not a public issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hamidihamza.com/notes/ajsf-angular-22-upgrade/" rel="noopener noreferrer"&gt;hamidihamza.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>forms</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Texaryn: one JSON Schema, rendered in React, Vue or a web component</title>
      <dc:creator>Hamza Hamidi</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:06:45 +0000</pubDate>
      <link>https://dev.to/hamzahamidi/texaryn-one-json-schema-rendered-in-react-vue-or-a-web-component-85c</link>
      <guid>https://dev.to/hamzahamidi/texaryn-one-json-schema-rendered-in-react-vue-or-a-web-component-85c</guid>
      <description>&lt;p&gt;Texaryn is a headless runtime for JSON Schema forms. A schema adapter reads Draft 7, 2019-09 or 2020-12, a framework-neutral core owns state, validation and submission, and a React, Vue or Web Components binding renders the result.&lt;/p&gt;

&lt;p&gt;If you've shipped react-jsonschema-form, here's what's different: array row identity lives in the runtime and follows the row's data or an &lt;code&gt;itemKey&lt;/code&gt; you name, each field chooses whether it validates on blur, change or submit, and the same runtime renders under React, Vue or a web component. It's pre-1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I've maintained &lt;a href="https://github.com/hamzahamidi/ajsf" rel="noopener noreferrer"&gt;AJSF&lt;/a&gt;, the Angular JSON Schema form library, since 2018. It continues &lt;code&gt;dschnelldavis/angular2-json-schema-form&lt;/code&gt; in its own repository: &lt;a href="https://github.com/hamzahamidi/ajsf/commit/b148449f07b5b475a993d24fc443d0c7a62555ab" rel="noopener noreferrer"&gt;the first commit&lt;/a&gt;, in July 2018, brought that code to Angular 6. Its major version follows the Angular major it targets, and &lt;a href="https://github.com/hamzahamidi/ajsf/releases/tag/v22.2.1" rel="noopener noreferrer"&gt;&lt;code&gt;22.2.1&lt;/code&gt;&lt;/a&gt; is on Angular 22.&lt;/p&gt;

&lt;p&gt;It has &lt;a href="https://github.com/hamzahamidi/ajsf" rel="noopener noreferrer"&gt;359 stars&lt;/a&gt; and &lt;a href="https://github.com/hamzahamidi/ajsf/forks" rel="noopener noreferrer"&gt;177 forks&lt;/a&gt;, and &lt;code&gt;@ajsf/core&lt;/code&gt; had &lt;a href="https://api.npmjs.org/downloads/point/2026-08-23:2026-09-21/@ajsf/core" rel="noopener noreferrer"&gt;17,630 npm downloads from 23 August to 21 September 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AJSF already keeps the schema logic apart from the design system (Material, Bootstrap, PrimeNG), but all of it runs inside Angular. Texaryn grew out of that, and out of exploring what AI needs to build interfaces: describe the interface as structured data, let a runtime own state and validation, and keep rendering in a thin layer per framework.&lt;/p&gt;

&lt;p&gt;In RJSF, &lt;code&gt;Form&lt;/code&gt; processes the schema, manages state and renders, all in React. I wanted three separate owners: an adapter that understands JSON Schema, a runtime that owns form state, and a binding that renders a framework-neutral description of the form.&lt;/p&gt;

&lt;p&gt;The first user I had in mind builds form-heavy React apps from JSON Schema: configuration screens, multi-step onboarding, admin panels, data entry tools. They want per-field dirty and touched state, validation timing per field, and custom components without forking a theme, in React today without being locked into React. Vue was the second binding on purpose: &lt;code&gt;setup&lt;/code&gt; runs once and reactivity is tracked per ref, where React re-runs a component on every render, so it shows whether the binding contract quietly assumed React's model.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSON Schema
    ↓  adapter (@texaryn/schema-json)
SchemaProjection
    ↓  compiler (@texaryn/core)
UIDocument
    ↓  runtime (@texaryn/core)
FormRuntime state
    ↓  binding (@texaryn/react, @texaryn/vue, @texaryn/web-components)
Widgets (a default set per binding; Bootstrap 5 and Material UI for React)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createJsonSchemaAdapter&lt;/code&gt; returns an object that implements &lt;code&gt;SchemaEvaluationPort&lt;/code&gt;, the interface the runtime calls to project and validate data. A projection is the fields, constraints and active branches for the current data, so it's recomputed as the user edits.&lt;/p&gt;

&lt;p&gt;The compiler turns the projection into a &lt;code&gt;UIDocument&lt;/code&gt;: a versioned tree of typed nodes that says what the form contains, not how React, Vue or the DOM draws it. A binding receives that document rather than the schema, so conditionals, composition and dependencies are evaluated in one place, the adapter, and no binding reimplements them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FormRuntime&lt;/code&gt; owns values, validation, dirty and touched state, visibility, disabled state, submission and array identity. Bindings subscribe to it instead of keeping their own copy. The core has no dependencies at all, and the UI definition is data: no generated JavaScript and no &lt;code&gt;eval()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the rendering side, a binding (&lt;code&gt;@texaryn/react&lt;/code&gt;, &lt;code&gt;@texaryn/vue&lt;/code&gt;, &lt;code&gt;@texaryn/web-components&lt;/code&gt;) connects the runtime to a framework. A registry maps each UI node to a component, and a widget set such as &lt;code&gt;@texaryn/react-bootstrap&lt;/code&gt; or &lt;code&gt;@texaryn/react-mui&lt;/code&gt; is a ready-made registry for React.&lt;/p&gt;

&lt;p&gt;The repository checks the neutrality claim: every example in its catalog runs through each binding and widget set, and the build fails when a documented feature has no example. The &lt;a href="https://texaryn.github.io/texaryn/playground/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; shows the same examples through React Default, React Bootstrap 5, React Material UI, Vue Default and Web Components Default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema
&lt;/h2&gt;

&lt;p&gt;All three examples render this &lt;code&gt;schema.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://json-schema.org/draft/2020-12/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;integer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;required&lt;/code&gt; only checks that the key exists, and &lt;code&gt;''&lt;/code&gt; counts as a value, so &lt;code&gt;minLength: 1&lt;/code&gt; is what rejects an empty name.&lt;/p&gt;

&lt;h2&gt;
  
  
  In React
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @texaryn/core @texaryn/schema-json @texaryn/react react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@texaryn/react&lt;/code&gt; needs &lt;a href="https://www.npmjs.com/package/@texaryn/react" rel="noopener noreferrer"&gt;React 18 or newer&lt;/a&gt;. The packages are ES modules only, and the examples await the adapter at module top level, which needs a build target that supports it, such as &lt;code&gt;es2022&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createJsonSchemaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/schema-json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ErrorSummary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FormProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FormRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createJsonSchemaAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada Lovelace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;validationTrigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FormProvider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorSummary&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FormRoot&lt;/span&gt; &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validating&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submitting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Submit
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FormProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useForm&lt;/code&gt; creates the runtime and subscribes to its stores, and &lt;code&gt;FormProvider&lt;/code&gt; makes it available to &lt;code&gt;ErrorSummary&lt;/code&gt; and &lt;code&gt;FormRoot&lt;/code&gt;. &lt;code&gt;FormRoot&lt;/code&gt; renders fields only, with no &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; around them, so the button dispatches the &lt;code&gt;Submit&lt;/code&gt; command itself and Enter doesn't submit. With an empty name, &lt;code&gt;onSubmit&lt;/code&gt; isn't called, and &lt;code&gt;ErrorSummary&lt;/code&gt; takes focus under the heading "There is a problem" and lists &lt;code&gt;Name: Value `#/name` should have a minimum length of `1`, but got `0`.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That message comes from the validator, and there's no way to rewrite it yet. The default widgets set &lt;code&gt;aria-invalid&lt;/code&gt; and link &lt;code&gt;aria-describedby&lt;/code&gt; through &lt;code&gt;getInputProps&lt;/code&gt; and &lt;code&gt;getErrorProps&lt;/code&gt;, which a custom widget can call too.&lt;/p&gt;

&lt;p&gt;Hints go in the same options as &lt;code&gt;initialData&lt;/code&gt;, keyed by JSON Pointer. A field hint sets &lt;code&gt;widget&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;placeholder&lt;/code&gt;, &lt;code&gt;helpText&lt;/code&gt; or &lt;code&gt;validationTrigger&lt;/code&gt; (&lt;code&gt;'blur'&lt;/code&gt;, &lt;code&gt;'change'&lt;/code&gt; debounced 300 ms by default, or &lt;code&gt;'submit'&lt;/code&gt;), and an array hint adds &lt;code&gt;itemKey&lt;/code&gt; and &lt;code&gt;canReorder&lt;/code&gt;. A field without a &lt;code&gt;validationTrigger&lt;/code&gt; validates only on submit, so in the Vue and web component examples below an error stays until the next submit.&lt;/p&gt;

&lt;p&gt;Submission is a state machine: &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;validating&lt;/code&gt;, &lt;code&gt;submitting&lt;/code&gt;, &lt;code&gt;submitted&lt;/code&gt;. Submit validates an immutable snapshot of the data, and an edit during &lt;code&gt;validating&lt;/code&gt; cancels the attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Vue
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @texaryn/core @texaryn/schema-json @texaryn/vue vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@texaryn/vue&lt;/code&gt; needs &lt;a href="https://www.npmjs.com/package/@texaryn/vue" rel="noopener noreferrer"&gt;Vue 3.5 or newer&lt;/a&gt;. &lt;code&gt;main.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createJsonSchemaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/schema-json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createJsonSchemaAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;App.vue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SchemaEvaluationPort&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ErrorSummary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FormRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;provideFormRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchemaEvaluationPort&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;provideFormRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ErrorSummary&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormRoot&lt;/span&gt; &lt;span class="na"&gt;:registry=&lt;/span&gt;&lt;span class="s"&gt;"registry"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
    &lt;span class="na"&gt;:disabled=&lt;/span&gt;&lt;span class="s"&gt;"submission.status === 'validating' || submission.status === 'submitting'"&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"dispatch(&lt;/span&gt;{ type: 'Submit' })"
  &amp;gt;
    Submit
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter is created in &lt;code&gt;main.ts&lt;/code&gt; and passed as a prop, so &lt;code&gt;setup&lt;/code&gt; stays synchronous and needs no &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;. &lt;code&gt;provideFormRuntime&lt;/code&gt; takes the place of &lt;code&gt;FormProvider&lt;/code&gt;, which is why &lt;code&gt;ErrorSummary&lt;/code&gt; and &lt;code&gt;FormRoot&lt;/code&gt; sit in this component's template: they have to be descendants of the component that provided the runtime.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@texaryn/vue&lt;/code&gt; ships render functions, so single-file components are your choice rather than a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  As a web component
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @texaryn/core @texaryn/schema-json @texaryn/web-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createJsonSchemaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/schema-json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defineTexarynForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/web-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TexarynFormElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@texaryn/web-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;defineTexarynForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createJsonSchemaAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;texaryn-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TexarynFormElement&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDefaultRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorSummary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;defineTexarynForm()&lt;/code&gt; registers the element explicitly: importing the package defines nothing. The element renders its own &lt;code&gt;&amp;lt;form novalidate&amp;gt;&lt;/code&gt; and turns a &lt;code&gt;submit&lt;/code&gt; event into the &lt;code&gt;Submit&lt;/code&gt; command, so a &lt;code&gt;type="submit"&lt;/code&gt; button inside it works. Set &lt;code&gt;options&lt;/code&gt; before &lt;code&gt;port&lt;/code&gt;: on an element that's already in the page, &lt;code&gt;options&lt;/code&gt; set after &lt;code&gt;port&lt;/code&gt; are ignored.&lt;/p&gt;

&lt;p&gt;The element emits &lt;code&gt;texaryn-data-change&lt;/code&gt; and &lt;code&gt;texaryn-submission-change&lt;/code&gt;. Instead of &lt;code&gt;port&lt;/code&gt; and &lt;code&gt;options&lt;/code&gt; it can take a &lt;code&gt;runtime&lt;/code&gt; you created, which it borrows and never destroys, so one runtime can render through several bindings (setting both throws). The package depends on &lt;code&gt;@texaryn/core&lt;/code&gt; only and loads no stylesheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What JSON Schema it understands
&lt;/h2&gt;

&lt;p&gt;The adapter reads Draft 7, 2019-09 and 2020-12. Draft 4 and Draft 6 aren't supported, and they aren't rejected either: a missing or unrecognized &lt;code&gt;$schema&lt;/code&gt; falls back to Draft 7, and &lt;code&gt;createJsonSchemaAdapter(schema, { defaultDialect })&lt;/code&gt; changes that fallback.&lt;/p&gt;

&lt;p&gt;Validation is delegated to &lt;code&gt;json-schema-library&lt;/code&gt;. The adapter asserts &lt;code&gt;format&lt;/code&gt; in Draft 7, which includes a schema with no &lt;code&gt;$schema&lt;/code&gt;, and never in 2019-09 or 2020-12, with no option to change that.&lt;/p&gt;

&lt;p&gt;The official JSON Schema Test Suite measures that validation, not which keywords get a form control. Mandatory tests passed by &lt;code&gt;@texaryn/schema-json&lt;/code&gt; at suite revision &lt;a href="https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/f6fd52a0a95472e079cbfc6ef7f089702b80e045" rel="noopener noreferrer"&gt;&lt;code&gt;f6fd52a&lt;/code&gt;&lt;/a&gt;, from the &lt;a href="https://texaryn.github.io/texaryn/guides/json-schema-conformance/" rel="noopener noreferrer"&gt;conformance page&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dialect&lt;/th&gt;
&lt;th&gt;Passed&lt;/th&gt;
&lt;th&gt;Mandatory tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Draft 7&lt;/td&gt;
&lt;td&gt;917&lt;/td&gt;
&lt;td&gt;929&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2019-09&lt;/td&gt;
&lt;td&gt;1,244&lt;/td&gt;
&lt;td&gt;1,261&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020-12&lt;/td&gt;
&lt;td&gt;1,278&lt;/td&gt;
&lt;td&gt;1,301&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every mandatory failure is external schema resolution: the suite expects remote documents from &lt;code&gt;http://localhost:1234&lt;/code&gt;, and the adapter has no resolver hook. It fails closed, so an unresolved reference is a &lt;code&gt;$ref&lt;/code&gt; validation error rather than a skipped check. The page deliberately gives no combined percentage.&lt;/p&gt;

&lt;p&gt;In practice, a schema that &lt;code&gt;$ref&lt;/code&gt;s another document fails validation wherever data reaches that reference, and a root that is only a remote &lt;code&gt;$ref&lt;/code&gt; makes &lt;code&gt;createFormRuntime&lt;/code&gt; throw &lt;code&gt;Schema projection missing root node&lt;/code&gt;. Bundle it into one document with local &lt;code&gt;$ref&lt;/code&gt;s first, which also work at the root.&lt;/p&gt;

&lt;p&gt;Projection has its own limits. Tuple arrays (&lt;code&gt;prefixItems&lt;/code&gt; in 2020-12, or an array-valued &lt;code&gt;items&lt;/code&gt; in Draft 7 and 2019-09) get no dedicated projection, &lt;code&gt;patternProperties&lt;/code&gt; and &lt;code&gt;additionalProperties&lt;/code&gt; generate no fields, and remote &lt;code&gt;$ref&lt;/code&gt;, &lt;code&gt;$dynamicRef&lt;/code&gt; and &lt;code&gt;$recursiveRef&lt;/code&gt; are out of scope. A &lt;code&gt;oneOf&lt;/code&gt; or &lt;code&gt;anyOf&lt;/code&gt; branch is chosen from the current data, so there's no branch picker, and schema &lt;code&gt;default&lt;/code&gt; values aren't written into the data unless you pass &lt;code&gt;initialization: 'schema-defaults'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from RJSF
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RJSF&lt;/th&gt;
&lt;th&gt;Texaryn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;schema&lt;/code&gt; prop&lt;/td&gt;
&lt;td&gt;&lt;code&gt;createJsonSchemaAdapter(schema)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uiSchema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hints keyed by JSON Pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;formData&lt;/code&gt; / &lt;code&gt;onChange&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;initialData&lt;/code&gt; option, &lt;code&gt;data&lt;/code&gt; store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;schema &lt;code&gt;default&lt;/code&gt; values in the data&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;initialization: 'schema-defaults'&lt;/code&gt; (off by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;widgets, templates, fields&lt;/td&gt;
&lt;td&gt;renderer registry, &lt;code&gt;WidgetComponent&lt;/code&gt;, &lt;code&gt;NodeRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;array rendering&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;useFieldArray&lt;/code&gt; with stable item identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;liveValidate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;validationTrigger&lt;/code&gt; hints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;customValidate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no equivalent yet: only the schema validates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onSubmit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;onSubmit&lt;/code&gt; option plus the submission lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;showErrorList&lt;/code&gt; / &lt;code&gt;ErrorList&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ErrorSummary&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;transformErrors&lt;/code&gt;, &lt;code&gt;extraErrors&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uiSchema: { email: { 'ui:placeholder': '...' } }&lt;/code&gt; becomes &lt;code&gt;hints: { '/email': { placeholder: '...' } }&lt;/code&gt;. &lt;code&gt;formContext&lt;/code&gt; becomes React context or props, and templates become registry entries.&lt;/p&gt;

&lt;p&gt;Adding, removing and moving rows keeps each row's identity. When &lt;code&gt;Reset&lt;/code&gt; replaces the data, rows are matched to their old identities by value, so an edited row gets a new identity and two identical rows can swap theirs, unless an &lt;code&gt;itemKey&lt;/code&gt; hint names a JSON Pointer inside each row, such as &lt;code&gt;'/id'&lt;/code&gt;, to match on instead. The &lt;a href="https://texaryn.github.io/texaryn/guides/migrating-from-rjsf/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; covers the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't there yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stability.&lt;/strong&gt; It's pre-1.0, and &lt;a href="https://www.npmjs.com/package/@texaryn/core?activeTab=versions" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/core&lt;/code&gt;&lt;/a&gt; went from its first publish on 30 August 2026 to 0.12.1 on 23 September, so pin exact versions. These examples run with &lt;a href="https://www.npmjs.com/package/@texaryn/core/v/0.12.1" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/core&lt;/code&gt; 0.12.1&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/@texaryn/schema-json/v/0.6.3" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/schema-json&lt;/code&gt; 0.6.3&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/@texaryn/react/v/0.5.2" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/react&lt;/code&gt; 0.5.2&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/@texaryn/vue/v/0.4.2" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/vue&lt;/code&gt; 0.4.2&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@texaryn/web-components/v/0.4.1" rel="noopener noreferrer"&gt;&lt;code&gt;@texaryn/web-components&lt;/code&gt; 0.4.1&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation beyond the schema.&lt;/strong&gt; No custom or async validators and no server errors after validation. The runtime accepts a port whose &lt;code&gt;validate&lt;/code&gt; returns a promise, but the published adapter only validates against the schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widget sets for Vue and Web Components.&lt;/strong&gt; Bootstrap 5 and Material UI exist for React only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web component integration.&lt;/strong&gt; The element renders its own &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; in light DOM, so it can't sit inside another &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;. It has no Shadow DOM, &lt;code&gt;formAssociated&lt;/code&gt; or &lt;code&gt;ElementInternals&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI generation tooling.&lt;/strong&gt; None ships yet; it's on the roadmap. Today a generated schema and hints go through &lt;code&gt;createJsonSchemaAdapter&lt;/code&gt; and &lt;code&gt;hints&lt;/code&gt; like hand-written ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://texaryn.github.io/texaryn/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/strong&gt;, starting with &lt;a href="https://texaryn.github.io/texaryn/start/getting-started/" rel="noopener noreferrer"&gt;getting started&lt;/a&gt; and the &lt;a href="https://texaryn.github.io/texaryn/concepts/architecture/" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://texaryn.github.io/texaryn/playground/" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;&lt;/strong&gt;: every catalog example through all five renderers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema details&lt;/strong&gt;: &lt;a href="https://texaryn.github.io/texaryn/guides/json-schema-support/" rel="noopener noreferrer"&gt;JSON Schema support&lt;/a&gt; and the &lt;a href="https://texaryn.github.io/texaryn/guides/json-schema-conformance/" rel="noopener noreferrer"&gt;validation test suite figures&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coming from RJSF&lt;/strong&gt;: the &lt;a href="https://texaryn.github.io/texaryn/guides/migrating-from-rjsf/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://texaryn.github.io/texaryn/api/" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/strong&gt;, the &lt;a href="https://www.npmjs.com/org/texaryn" rel="noopener noreferrer"&gt;packages on npm&lt;/a&gt; and &lt;a href="https://texaryn.github.io/texaryn/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; for coding agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/texaryn/texaryn" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/strong&gt; and the &lt;a href="https://github.com/texaryn/texaryn/blob/main/ROADMAP.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and hit a missing extension point, a confusing renderer contract or a schema that doesn't project the way you expect, &lt;a href="https://github.com/texaryn/texaryn/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hamidihamza.com/notes/texaryn-json-schema-forms/" rel="noopener noreferrer"&gt;hamidihamza.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>vue</category>
      <category>typescript</category>
      <category>webcomponents</category>
    </item>
  </channel>
</rss>
