A form that explains itself
The same fields most sign-up forms have — with messages that say what to fix, and formatting applied when you leave the field.
<form class="demo" id="form" novalidate>
<div class="demo-field">
<label class="demo-label" for="eid">Emirates ID</label>
<input id="eid" class="demo-input" data-kind="emirates-id" placeholder="784-1990-1234567-6" autocomplete="off">
<span class="demo-error" data-for="eid"></span>
</div>
<div class="demo-field">
<label class="demo-label" for="trn">Tax registration number</label>
<input id="trn" class="demo-input" data-kind="uae-trn" placeholder="100 1234 5678 9012" autocomplete="off">
<span class="demo-error" data-for="trn"></span>
</div>
<div class="demo-field">
<label class="demo-label" for="iban">IBAN (UAE or Saudi only)</label>
<input id="iban" class="demo-input" data-kind="iban" data-only="AE,SA" placeholder="AE07 0331 2345 6789 0123 456" autocomplete="off">
<span class="demo-error" data-for="iban"></span>
</div>
<div class="demo-row">
<button class="demo-btn" type="submit">Submit</button>
<button class="demo-btn" type="button" id="fill">Fill with valid data</button>
</div>
<p class="demo-out" id="out">Nothing submitted yet.</p>
</form>
import { generate, validate } from 'https://www.devix.pk/cdn/oss/validators@1.0.0/index.js';
const MESSAGES = {
empty: 'This one is required.',
'too-short': 'That is too short — check you copied all of it.',
'too-long': 'That is longer than it should be.',
'bad-characters': 'There are characters in there that do not belong.',
'bad-structure': 'That does not look right — check the format in the placeholder.',
'bad-checksum': 'A digit is wrong: the check digit disagrees.',
'unknown-country': 'We can only take an account in the UAE or Saudi Arabia.',
};
const fields = [...document.querySelectorAll('[data-kind]')];
const errorFor = (field) => document.querySelector(`[data-for="${field.id}"]`);
const check = (field, quiet = false) => {
const options = field.dataset.only ? { only: field.dataset.only.split(',') } : {};
const result = validate(field.dataset.kind, field.value, options);
const message = result.valid ? '' : (MESSAGES[result.reason] ?? 'That does not look right.');
// Nothing is marked wrong while the field is still being filled in.
if (!quiet) {
errorFor(field).textContent = message;
field.setAttribute('aria-invalid', String(!result.valid && field.value !== ''));
}
return result;
};
for (const field of fields) {
field.addEventListener('input', () => {
if (errorFor(field).textContent) check(field);
});
field.addEventListener('blur', () => {
const result = check(field);
if (result.valid) field.value = result.formatted;
});
}
document.getElementById('fill').addEventListener('click', () => {
for (const field of fields) {
field.value = field.dataset.kind === 'iban' ? generate('iban', { country: 'AE' }) : generate(field.dataset.kind);
check(field);
}
});
document.getElementById('form').addEventListener('submit', (event) => {
event.preventDefault();
const results = fields.map((field) => [field.id, check(field)]);
const bad = results.filter(([, result]) => !result.valid);
document.getElementById('out').textContent = bad.length
? `Not sent: ${bad.map(([id, result]) => `${id} (${result.reason})`).join(', ')}`
: `Sent: ${results.map(([id, result]) => `${id}=${result.value}`).join(' · ')}`;
});
.demo-input[aria-invalid='true'] { border-color: var(--demo-danger); }
.demo-error:empty { display: none; }