Navigation

Forms and validation

Every Oblyx form control renders a real native input inside its light DOM: an <input>, an <input type="checkbox">, an <input type="radio">. That native element is what joins form.elements, FormData, and the browser's own constraint validation, identically whether it was on the page at load or arrived later in an HTMX swap. Nothing collects or serializes a value on the client; a plain <form> POST finds every control's value the same way it would find a hand-written <input>'s.

A labeled input

label, help and name are plain attributes. The label is always a real <label for> pointing at the control, not decorative text, and help is wired into aria-describedby automatically.

Input with label and help text

HTMLKotlin
<oblyx-input label="Display name" help="Shown on your public profile." name="displayName"></oblyx-input>
oblyxInput(label = "Display name", help = "Shown on your public profile.", name = "displayName")

Constraint validation

required, pattern, minlength, maxlength and type forward verbatim onto the underlying native <input>, so the browser's own validation applies: :invalid styling, reportValidity(), and submit-blocking all work with zero Oblyx JavaScript. Submit the form below empty to see the browser's native validation message.

Required email input

Submitting with this field empty triggers the browser's own validation UI.

HTMLKotlin
<oblyx-input label="Work email" name="email" type="email" required></oblyx-input>
oblyxInput(label = "Work email", name = "email", type = OblyxInputType.EMAIL, required = true)

The error state

error is a separate, purely informational attribute: setting it puts the control in its error visual state and sets aria-invalid="true". It does not call setCustomValidity() and does not, on its own, block submission. Use it to display a validation result that came back from elsewhere, such as a server response to a previous submission, alongside or instead of native constraint validation.

Input showing a server-reported error

HTMLKotlin
<oblyx-input label="Username" name="username" value="a" error="Must be at least 3 characters."></oblyx-input>
oblyxInput(label = "Username", name = "username", value = "a", error = "Must be at least 3 characters.")

No field wrapper

There is no oblyx-field element. Every form control takes label,help and error directly as its own attributes, rather than being wrapped in a separate labeling element. Astryx's own Field/FieldLabel/FieldStatus family contributes little beyond static layout, a label, a description and a status box in fixed order, and its one genuinely coordinated behavior (a horizontal-labels layout mode) is expressed here as a plain CSS descendant selector instead of the wrapper's own coordination logic. Every control's label, help and error markup shares one class convention (.ox-field__label, .ox-field__help,.ox-field__error) so the visual result is identical across controls without a wrapper element gluing them together.

Checkbox

A checkbox submits its value attribute (default: the browser's own "on") under its name only when checked, exactly like a hand-written <input type="checkbox">; an unchecked checkbox contributes nothing to FormData at all.

Checkbox

HTMLKotlin
<oblyx-checkbox label="Send me product updates" name="updates"></oblyx-checkbox>
oblyxCheckbox(label = "Send me product updates", name = "updates")

Radio grouping via name

There is no oblyx-radio-group element. Any number of <oblyx-radio> instances that share a name attribute are automatically one group, exactly the way plain <input type="radio"> elements group: mutual exclusion, arrow-key roving navigation between them, and which one's value ends up in FormData are all native browser behavior, not Oblyx bookkeeping. Setting required on any radio in the group requires one member of that group to be checked before the form submits, enforced by the browser across the whole group.

This grouping is deliberate for HTMX: a fragment routinely swaps in a single <oblyx-radio> with no siblings and no group wrapper present, which is the normal shape of an HTMX response, not an edge case. Because grouping lives entirely in the name attribute, read by the browser rather than by any Oblyx JavaScript, a lone swapped-in radio already has everything it needs. See HTMX and server-rendered fragments for the general version of this rule.

A radio group

HTMLKotlin
<oblyx-radio label="Email" name="contact-method" value="email" checked></oblyx-radio>
<oblyx-radio label="Phone" name="contact-method" value="phone"></oblyx-radio>
<oblyx-radio label="No contact" name="contact-method" value="none"></oblyx-radio>
oblyxRadio(label = "Email", name = "contact-method", value = "email", checked = true)
oblyxRadio(label = "Phone", name = "contact-method", value = "phone")
oblyxRadio(label = "No contact", name = "contact-method", value = "none")

A complete form, no JavaScript required to submit

The example below reads out its own submitted FormData so you can see exactly what reaches the server. The inline script that does the reading is here purely for this demo, so the page doesn't navigate away; collecting the values themselves is entirely native browser behavior, not Oblyx code.

Name, subscribe checkbox, and a contact-method radio group

Submit the form to see FormData's contents printed below it.

Submit
HTMLKotlin
<form onsubmit="event.preventDefault(); var data = new FormData(this); var pairs = []; data.forEach(function (v, k) { pairs.push(k + '=' + v); }); this.querySelector('output').textContent = pairs.length ? pairs.join(', ') : 'No data submitted.';">
  <oblyx-input label="Full name" name="name" required></oblyx-input>
  <oblyx-checkbox label="Subscribe to the newsletter" name="subscribe"></oblyx-checkbox>
  <oblyx-radio label="Email me" name="contact-method" value="email" checked></oblyx-radio>
  <oblyx-radio label="Don't contact me" name="contact-method" value="none"></oblyx-radio>
  <oblyx-button type="submit">Submit</oblyx-button>
  <output></output>
</form>
form {
  oblyxInput(label = "Full name", name = "name", required = true)
  oblyxCheckbox(label = "Subscribe to the newsletter", name = "subscribe")
  oblyxRadio(label = "Email me", name = "contact-method", value = "email", checked = true)
  oblyxRadio(label = "Don't contact me", name = "contact-method", value = "none")
  oblyxButton(type = OblyxButtonType.SUBMIT) { +"Submit" }
}

Reference

<oblyx-input>

AttributeTypeDefaultDescription
labelstring | undefinedLabel text, always rendered and always associated with the input via a real <label for>. Required for an accessible name; omitting it is a defect and is flagged loudly rather than silently producing an unlabeled input.
helpstring | undefinedHelp text, rendered between the label and the input box and wired into aria-describedby.
errorstring | undefinedError message. Presence alone, independent of error being non-empty text, puts the input in its error visual state and sets aria-invalid="true". When non-empty, also renders the message wired into aria-describedby. Purely informational: this does not call setCustomValidity or block submission.
sizesmmdlgmdForm-control height.
typetextpasswordemailtextNative <input type>, forwarded verbatim.
namestring | undefinedForwarded verbatim to the native <input name>. This is what makes the value appear in FormData/receiveParameters().
valuestring | undefinedInitial value only, applied as an attribute binding rather than a property binding so it never clobbers what the user has typed on a later re-render.
placeholderstring | undefinedForwarded verbatim to the native <input placeholder>.
requiredbooleanfalseForwarded to native required. Enforces real constraint validation.
patternstring | undefinedForwarded verbatim to native <input pattern>. Enforces real constraint validation, matching required/minlength/maxlength.
minlengthstring | undefinedForwarded verbatim to native <input minlength>. Enforces real constraint validation, matching required/pattern/maxlength.
maxlengthstring | undefinedForwarded verbatim to native <input maxlength>. Enforces real constraint validation, matching required/pattern/minlength.
disabledbooleanfalseNative disabled state, forwarded verbatim to the inner <input>.
readonlybooleanfalseNative readonly state, forwarded verbatim to the inner <input>.
autocompletestring | undefinedForwarded verbatim to the native <input autocomplete>.

<oblyx-checkbox>

AttributeTypeDefaultDescription
labelstring | undefinedLabel text, always rendered and associated with the native input via a real <label for>. Required for an accessible name; omitting it is a defect and is flagged loudly rather than silently producing an unlabeled control.
helpstring | undefinedHelp text, rendered below the control and wired into aria-describedby.
errorstring | undefinedError message. Presence alone (independent of error being non-empty text) sets aria-invalid="true"; non-empty text also renders the message wired into aria-describedby, matching oblyx-input's error contract.
sizesmmdmdBox size.
namestring | undefinedForwarded verbatim to the native <input name>. Makes the checked state appear in FormData/receiveParameters().
valuestring | undefinedForwarded verbatim to the native <input value>. Native checkboxes submit "on" when checked if this is left unset. Set it only when a consumer genuinely needs a non-default submitted value.
checkedbooleanfalseInitial checked state only. Kept in sync with the live native state on every change event, so reading checkboxEl.checked afterward reflects reality.
indeterminatebooleanfalseTri-state mixed indicator. Cleared automatically by native browser behavior the moment the user toggles the control, and kept in sync via the change listener.
requiredbooleanfalseForwarded to native required. An unchecked required checkbox blocks submission natively.
disabledbooleanfalseNative disabled state, forwarded verbatim to the inner <input>. A disabled checkbox is excluded from FormData, matching native behavior.

<oblyx-radio>

AttributeTypeDefaultDescription
labelstring | undefinedLabel text, always rendered after the control and always associated with the input via a real <label for>. Required for an accessible name; omitting it is a defect and is flagged loudly rather than silently producing an unlabeled radio.
helpstring | undefinedHelp text, rendered under the control and wired into aria-describedby.
errorstring | undefinedError message. Presence alone puts the control in its error state (aria-invalid="true") and, when non-empty, renders the message wired into aria-describedby, same contract as oblyx-input.error.
sizesmmdmdVisual size.
namestring | undefinedForwarded verbatim to the native <input name>, the entire grouping mechanism. Radios sharing this value form one native group with mutual exclusion and arrow-key navigation.
valuestring | undefinedForwarded verbatim to the native <input value>. What actually appears in FormData under name when this radio is the checked one in its group.
checkedbooleanfalseInitial checked state only, bound as the boolean checked HTML attribute (?checked=${...}), never as the .checked IDL property. Per the HTML spec, a radio's checkedness has its own "dirty checkedness flag": once the user clicks it (or the browser un-checks it because a same-named sibling was clicked instead), the live .checked property detaches from the checked content attribute. Re-rendering with toggleAttribute('checked', ...) on every unrelated property change (e.g. toggling error) can therefore never fight the user's or the browser's own native radio-group selection.
requiredbooleanfalseForwarded to native required, enforcing real constraint validation. Per the HTML spec, required on any radio in a name group requires one member of that group to be checked before the group's form can submit; the browser enforces this natively across the whole group.
disabledbooleanfalseNative disabled state, forwarded verbatim to the inner <input>. A disabled radio is excluded from FormData and from the group's arrow-key navigation, matching plain HTML.