GUIDE
Forms & validation
The Dojo NG form controls are form-associated custom elements. Put them in a native <form> and they behave like built-in inputs: they submit their values, they block submission while they are invalid, and the browser shows its own messages. You do not need a form library.
Submitted values
Nothing yet. Try submitting the empty form first.
FormData.Use a native form
Give each control a name, and put the controls in a <form>. On submit, read the values with FormData, as you would for built-in inputs. Without preventDefault(), the browser posts the form to its action as usual.
<form id="signup">
<dj-text-input name="name" label="Name" required></dj-text-input>
<dj-email-input name="email" label="Email" required></dj-email-input>
<dj-checkbox name="terms" required>I accept the terms</dj-checkbox>
<button type="submit">Sign up</button>
</form>
<script type="module">
import "@dojo-ng/text-input";
import "@dojo-ng/email-input";
import "@dojo-ng/checkbox";
document.getElementById("signup").addEventListener("submit", (event) => {
event.preventDefault(); // remove this line to let the browser post the form
const data = Object.fromEntries(new FormData(event.target));
console.log(data); // { name: "Ada", email: "ada@example.com", terms: "on" }
});
</script>
- Each control's
<input>is inside its shadow root, so the control itself takes part in the form. A control with nonamesubmits nothing. - A
<fieldset disabled>disables the controls inside it, and disabled controls submit nothing. - A form reset returns a text input or text area to its
valueattribute, and a checkbox, switch, or radio to itscheckedstate from the HTML. - After back and forward navigation, and after autofill, the browser restores each control's value.
- For file uploads, set
enctype="multipart/form-data"on the form, as for a native file input.
What each control submits
Each control adds entries to the form's FormData under its name:
| Controls | What they submit |
|---|---|
| Text inputs, text area | The value as a string. An empty field submits an empty string. |
| Select, native select, typeahead, date input, time picker, calendar, list | The value as a string. Nothing when no value is chosen. |
| Number input, slider, rate | The number as a string, such as "30". |
| Checkbox, switch | Their value (default "on") when checked. Nothing when not checked. |
| Radio group | The value of the selected radio. Nothing when none is selected. |
| Checkbox group, chip typeahead | One entry for each selected value, all under the same name. Read them with formData.getAll(name). |
| Range slider | Two entries: name_min and name_max. |
| File input | The selected File. With multiple, one entry for each file. |
| Color picker | The color string, in its format. |
| Rich text | The document, in its format (HTML by default). |
The search box is not form-associated, because a search usually runs without submitting a form. Read its query property instead.
Built-in rules
The text inputs accept the same rules as a native <input>: required, pattern, min, max, step, minlength, and maxlength. type="email" and type="url" check the format. dj-email-input, dj-number-input, and dj-password-input are text inputs with the type already set.
<dj-text-input name="zip" label="ZIP code" required pattern="[0-9]{5}"></dj-text-input>
<dj-number-input name="seats" label="Seats" min="1" max="8"></dj-number-input>
<dj-password-input name="password" label="Password" required minlength="12"></dj-password-input>
<dj-text-input name="site" label="Website" type="url"></dj-text-input>
requiredalso works on the checkbox, checkbox group, radio group, select, native select, typeahead, date input, time picker, file input, and text area. A required checkbox group needs at least one option checked.- The browser checks
minlengthandmaxlengthonly after the user edits the field. A value set from code is not checked against them. - An invalid control blocks the form's
submitevent, so your handler only runs with valid values. - The messages come from the browser, in the browser's language, not the page's. For your own wording, use a custom rule.
Custom rules
dj-constrained-input is a text input with a validator property. The function receives the value and returns an error message, or undefined when the value is valid. The message takes part in form validation like a built-in rule, so an invalid value blocks the submit.
<dj-constrained-input id="code" name="code" label="Team code"
helper-text="Three capital letters, such as ABC."></dj-constrained-input>
<script type="module">
import "@dojo-ng/constrained-input";
// Return a message when the value breaks the rule. Return undefined when it is valid.
document.getElementById("code").validator = (value) =>
value && !/^[A-Z]{3}$/.test(value) ? "Use three capital letters." : undefined;
</script>
- The rule runs each time the value changes, and the message replaces the helper text while the field is invalid.
- Set
validatorfrom JavaScript. A function cannot be an attribute. - The function runs on every keystroke, so keep it fast. Check slow rules, such as "is this name taken?", on the server after submit.
dj-password-inputis built on the same element, so it also accepts avalidator.
For an error that only the server can find, call setCustomValidity() on any text input or text area, as on a native input. A non-empty message makes the field invalid until you clear it with an empty string:
const user = form.elements.namedItem("user");
// The server says the name is taken: show it on the field.
user.setCustomValidity("That name is taken.");
user.reportValidity();
// Clear it when the user changes the value.
user.addEventListener("input", () => user.setCustomValidity(""), { once: true });
When errors show
A field that the user has not touched is not marked as an error. A control shows its invalid state in one of two cases:
- The user edits the field and leaves it with an invalid value. A text input then turns red and shows the message under the field.
- The user submits the form. The browser shows its message on the first invalid control, and every invalid control is marked as user-invalid. A text input or text area also turns red and shows its message.
A form reset clears the user-invalid marks.
Style the invalid state
Page CSS cannot reach the <input> inside a shadow root, so :invalid does not work on these controls. Each control mirrors its state onto its own element instead, as attributes: data-dj-required, data-dj-valid, data-dj-invalid, data-dj-user-valid, and data-dj-user-invalid. Style the user- pair, so that untouched fields stay quiet.
/* A red outline on any control the user has left invalid */
form [data-dj-user-invalid] {
outline: 2px solid var(--dj-color-danger-600);
outline-offset: 2px;
border-radius: 4px;
}
/* The same with custom states, in browsers that support them */
dj-checkbox:state(user-invalid) {
color: var(--dj-color-danger-600);
}
The theming guide shows more, such as a hint that appears next to an invalid field.
Check validity from code
The controls have the same validity API as a native input, and the form's methods include them:
const form = document.getElementById("signup");
form.checkValidity(); // false while any control is invalid; marks them as user-invalid
form.reportValidity(); // the same, and the browser shows its message on the first one
const email = form.elements.namedItem("email");
email.validity.valueMissing; // true when the required field is empty
email.validationMessage; // the browser's message, in the browser's language
Labels, help text, and required marks
labelsets the visible label and the accessible name. Withlabel-hidden, the label is hidden on screen and still read by screen readers.helper-textshows a hint under a text input. While the field shows as invalid, the error message takes its place.- When it is
required, a text input, text area, select, native select, checkbox, checkbox group, or radio group marks its label with an asterisk. - For the checkbox, switch, and radio, the text in the default slot is the label.
dj-form
dj-form lays out its fields in a row, or in a column with column. Its submit() method emits dj-submit with a { name: value } object, and Enter in a field submits too.
submit()checks the fields first. While one is invalid, the browser shows its message,dj-submitis not emitted, andsubmit()returnsfalse. Setnovalidateto skip the check.- The values follow the same rules as a native form: an unchecked checkbox or switch and disabled controls are left out, and a name used by several checked controls gives an array.
- Enter in a text area adds a new line. It does not submit.
- Use a native form instead when you need to post to a URL or want
FormData. A native form can sit insidedj-formfor its layout.
These rules need @dojo-ng/form 0.1.2 or later.
<dj-form column>
<dj-text-input name="first" label="First name"></dj-text-input>
<dj-text-input name="last" label="Last name" required></dj-text-input>
</dj-form>
<script type="module">
import "@dojo-ng/form";
import "@dojo-ng/text-input";
document.querySelector("dj-form").addEventListener("dj-submit", (event) => {
console.log(event.detail.data); // { first: "…", last: "…" }, only when valid
});
</script>