Skip to content
kitn AI/UI

Form

kai-form

Point a JSON Schema at <kai-form> and get a fully themed, accessible, client-validated form — labels, widgets, submit button, and a read-only resolved state — without writing a single input element.

  • Shadow DOM
  • JSON Schema driven
  • Client-side validation
  • Read-only resolved state
  • Card contract events

Assign the form definition in JavaScript — it’s an object, not an HTML attribute:

<kai-form id="my-form"></kai-form>
<script type="module">
import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-form');
const form = document.querySelector('#my-form');
form.data = {
type: 'object',
title: 'How did we do?',
required: ['rating'],
'x-kai-submitLabel': 'Send feedback',
properties: {
rating: { type: 'integer', title: 'Overall rating', minimum: 1, maximum: 5, 'x-kai-widget': 'rating' },
comments: { type: 'string', title: 'Comments', 'x-kai-widget': 'textarea' },
},
};
form.addEventListener('kai-card', (e) => {
if (e.detail.kind === 'submit') console.log('submission data', e.detail.data);
if (e.detail.kind === 'action') console.log('secondary action', e.detail.action);
});
</script>
  • Schema → widgets deterministically: string → text; string + enum → radio/select; integer + x-kai-widget: 'rating' → stars; boolean → switch; array → repeater/checkbox group; nested object → fieldset. See the Every widget example for the full mapping.
  • x-kai-* hints in the schema control field order (x-kai-order), submit label (x-kai-submitLabel), secondary buttons (x-kai-actions), placeholder (x-kai-placeholder), and dismissibility (x-kai-dismissible).
  • Events use the Card contractkai-form fires no events of its own; it emits a single bubbling kai-card CustomEvent. Relevant kinds: submit (detail.data is the validated object), action, ready, dismiss, and error.
  • resolution — set el.resolution = { kind: 'submit', data: { … } } to swap the form for a read-only <dl> summary.

Star rating, free-text textarea, enum radio group, boolean switch, and a secondary Skip action.

Exercises the full schema-to-widget mapping: text, email, URL, date, password, select, number, slider, rating, switch, checkbox, tag list, checkbox group, repeater, and nested fieldset.

Required fields, minLength/maxLength, minimum/maximum, and format: 'email' — submit empty to see inline errors.

After a successful submission, set el.resolution to render a read-only <dl> summary with a “Submitted” badge.

Pass a non-object schema to see the inline error state; the element also emits kai-card with kind: 'error'.

PropertyTypeDefaultNotes
theme'auto'Color mode (`auto` follows prefers-color-scheme).
dataThe form definition: a JSON Schema (`type:'object'`) + `x-kai-*` UI hints (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for the full shape. It IS self-referential (`FormField.properties` is another `FormField` map), and the generated `element-types.d.ts` inlines every named type, so the shipped declaration bottoms out in a `Record<string, unknown>` placeholder one level down rather than carrying the recursion. That is why `FormDefinition` is a `type` alias: an interface gets no implicit index signature, so it would not be assignable to that placeholder.
cardIdStable card id correlating every emitted CardEvent. Attribute: `card-id`.
headingHeading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`.
resolutionSet when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'submit', data:{…} }`.
valuesControlled field values (JS property). When set, it wins over local edits.
defaultValuesInitial values overlaying the schema defaults (uncontrolled seed; JS property).
disabledfalseDisable all fields + submit. Attribute: `disabled`.
MethodSignatureNotes
(options?: FocusOptions): voidFocus the first control, or the first INVALID control after a failed validation.
(): voidValidate + submit programmatically: focus the first invalid field on failure, else emit the `submit` CardEvent and resolve. Named `send`, not `submit`.
(): voidRun client-side validation now and return `{ valid, errors? }` WITHOUT submitting.
(): voidRe-seed the form from each field's `default` and clear errors.
(): voidTrigger the dismiss path (emit `dismiss` + collapse to the re-openable stub).
(): voidRe-open a dismissed card from its stub (emit `reopen`).

This element wraps these SolidJS components — reach for them directly when you need finer control than the props expose.

Form