Skip to content
bahtuiv0.6.0

<baht-form>

The L1 engine: one dumb controlled form. JSON in → UI out; emits baht-change / baht-validity. No buttons, no cross-field logic.

JSON in, form out

Build this JSON visually in the Builder, then copy it here.

<baht-form></baht-form>

<script>
  const el = document.querySelector('baht-form');
  el.form = {
    "form_id": "contact",
    "form_name": "Contact",
    "fields": [
      {
        "order": 1,
        "type": "text",
        "key": "name",
        "label": "Name",
        "width": "1/2",
        "required": true,
        "value": ""
      },
      {
        "order": 2,
        "type": "text",
        "key": "email",
        "label": "Email",
        "width": "1/2",
        "required": true,
        "value": ""
      },
      {
        "order": 3,
        "type": "dropdown",
        "key": "topic",
        "label": "Topic",
        "width": "full",
        "options": [
          {
            "key": "bug",
            "label": "Bug report"
          },
          {
            "key": "feature",
            "label": "Feature request"
          }
        ],
        "value": null
      },
      {
        "order": 4,
        "type": "textarea",
        "key": "message",
        "label": "Message",
        "rows": 4,
        "value": ""
      }
    ]
  };
</script>

Read-only display

Same JSON, shown as values: option keys become labels, passwords are masked, missing answers read "Not provided". getData() still returns the full data.

<baht-form readonly="true"></baht-form>

<script>
  const el = document.querySelector('baht-form');
  el.form = {
    "form_id": "contact-ro",
    "form_name": "Contact",
    "fields": [
      {
        "order": 1,
        "type": "text",
        "key": "name",
        "label": "Name",
        "width": "1/2"
      },
      {
        "order": 2,
        "type": "email",
        "key": "email",
        "label": "Email",
        "width": "1/2"
      },
      {
        "order": 3,
        "type": "dropdown",
        "key": "topic",
        "label": "Topic",
        "options": [
          {
            "key": "bug",
            "label": "Bug report"
          },
          {
            "key": "feature",
            "label": "Feature request"
          }
        ]
      },
      {
        "order": 4,
        "type": "password",
        "key": "secret",
        "label": "API secret"
      },
      {
        "order": 5,
        "type": "textarea",
        "key": "message",
        "label": "Message"
      }
    ]
  };
  el.value = {
    "name": "Malee Srisuk",
    "email": "malee@example.com",
    "topic": "bug",
    "secret": "hunter2"
  };
</script>
NameTypeDefaultDescription
formJSFormDefinition{ form_id, form_name, fields[] }
valueJSRecord<string, unknown>{}Controlled values
registryJSRegistry{}Custom formatters/validators
show-optionalbooleanfalseLabel every non-required field "(optional)" (localized) instead of relying on asterisks alone.
optionsJSBahtFormOptions{}{ validateOn?: 'change'|'blur'|'submit', inputDebounce?: number }
fieldStatesJSRecord<string, DerivedFieldState>{}Per-field UI state from L2
viewstring | undefinedName of a view from `form.views`. Display-only: when set, only that view's fields are rendered and validated (the user can only fix what they see), but getData() still returns the FULL form — the server always validates the complete schema, views never change the submit payload.
flagsJSFeatureFlags{}Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as if they were never in the schema: no render, no validation, no payload.
readonlybooleanfalseRender every field as a display value (not a disabled form). Per-field readonly comes from the descriptor or a rule
EventdetailDescription
baht-validity{ valid, errors[] }Validity recomputed
baht-change{ form_name, key, value, data }Any field edited
nameCustomEvent