Beyond the attributes of Validation and Autofill, script adds cross-field rules and controls how a form is submitted.
<form action="/age" style="font-family: system-ui, sans-serif">
<label>Age <input name="age" type="number" min="0" required></label>
<label><input name="consent" type="checkbox"> Parent agrees</label>
<button name="go">Proceed</button> <button name="back" formaction="/" formnovalidate>Back</button>
</form>
<pre id="log"></pre>
<script type="module">
const form = document.querySelector('form');
const { age, consent, go, back } = form.elements;
const log = (text) => document.querySelector('#log').append(`${text}\n`);
age.value = '9'; // '' below marks the field valid again
age.setCustomValidity(age.value < 13 && !consent.checked ? 'Under-13s need a parent.' : '');
form.addEventListener('invalid', (e) => { e.preventDefault(); log(e.target.validationMessage); },
true); // invalid does not bubble: capture it
form.addEventListener('submit', (e) => {
e.preventDefault(); // demo: stay on the page
log(`submitted by ${e.submitter.name}`);
});
form.requestSubmit(go); // blocked by the custom error
form.requestSubmit(back); // formnovalidate skips validation
</script>
requestSubmit(submitter) (Baseline widely available since September 2022) acts like a click: it honors formnovalidate and formaction, validates, and fires submit with event.submitter; form.submit() does none of that. Always repeat the checks on the server.