Or: ‘Why UI libraries exist’

In working with web forms, the same sorts of edge cases seem to appear repeatedly, and (at least) I find myself waffling on how to approach them with JavaScript.

I feel better not reinventing the wheel, and just normalizing the cross-browser differences.

It's hard to avoid reinventing the wheel

The timing and intercepting of validation events feels hacky at best, yet I feel sloppy ignoring the browser's built in input validation. That being said, input validation is not consistently supported, not does it behave predictably. Also the UX leaves a lot to be desired, with forms opting to wait until an entire form has been submitted, rather than validating inline.

Binary validation isn't enough

The form and input specs only provide 2 values for validity state: :valid and :invalid , which are only evaluated after the input has had onBlur triggered as a target, unless it's also marked as :required which is evaluated on submit as :invalid .

However: this is not always true.

"an empty string means the constraint is satisfied".

– MDN: Constraint validation

I have seen folks like @joshblack use Objects like Enum s with 4 states, which seems like a much better idea.

'valid' | 'invalid' | 'validating' | 'initial';

We have CSS pseudo-classes for things like :out-of-range for min - max ranges, and :indeterminate for half-filled checkboxes, but not a breakdown of pseudo-classes for validity of initial states.

DOM validation APIs

The DOM method of event.target.validity is read-only, as is the ValidityState method. The latter is a real bummer, because that could be super useful.

You can only read from checkValidity() as well. There's no programmatic way in the browser to force set a field's validity. Update: I was wrong about this. Here's how you force the browser to apply :valid and :invalid on your terms.

In a handful of browsers, setCustomValidity() sounds like a great idea, ~~but it's only setting the content of the browser's default field validation message.~~

Any non-null value passed as a parameter to setCustomValidity() applies the browser's :invalid CSS pseudo-class. field.setCustomValidity("This field can't be empty");

To restore the browser's :valid CSS pseudo-class, pass an empty string to setCustomValidity() . field.setCustomValidity("");

Do note, however, that this API is not universally-supported across every browser, but it still has enough to justify inlcusion.

The other curious property is HTMLInputElement.validationMessage. This is yet another read-only property, that only appears while hovering over an invalid element.

You cannot set it, though it does change. It's an empty string when the input is valid, but the browser sets it again when it's invalid. It's a "getter". There's not even an error when you attempt to set it, at least in the console, anyway.

"There is no standard way to change their look and feel with CSS."

– MDN - Data form validation

Sure, you can start juggling classes like .invalid , .required , etc. but I can assure you: that is a guaranteed path to madness.

What about accessibility?

None of the aforementioned even touches how radically-inaccessible all of this is. Because of all the wheel reinvention needed to make inputs a reasonably-painless experience and consistent across various contexts, we've totally munged the native affordances given to us by HTML. This leads devs who care and notice to start applying attributes like aria-live , and accessibility experts to pull out their hair.

We have to do better.

Smarter defaults

Here are some ideas on how to improve developer and user experience at the W3C standards level.

Automatically disable autocorrect , autocaptialize , on email , url , password , and every other input type that isn't text or search .

Ditch placeholder , repurpose them to static input masks. Improve the email type validation regex. The pattern in the spec

/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/

allows this:



Search the web for all of 5 seconds and you'll find a better regex. Like this one. [a-zA-Z0-9_]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?!([a-zA-Z0-9]*\.[a-zA-Z0-9]*\.[a-zA-Z0-9]*\.))(?:[A-Za-z0-9](?:[a-zA-Z0-9-]*[A-Za-z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?

Ditch password masking. At the very least, include a hide password UI control that browsers can safeguard against saved password exploits. Use this same input type for captchas.

Introduce input type="fingerprint" . Native mobile operating systems are already doing this. Introduce UI counterparts on the Web for inputs that were born on native mobile interfaces. Toggles. Binary—on or off.

Segmented controls. Perhaps a type of radio button or select .

button or . geolocation input type. We already have geolocation APIs. Why not standardize the inputs? step attributes could allow finer-grained controls.

input type. We already have geolocation APIs. Why not standardize the inputs? attributes could allow finer-grained controls. Default form CSS to mobile-first styling

Large, separated touch targets

Picker menus instead of separate input s for month , day , time , etc. Make time data more intelligent Introduce birthdate types that automatically set max to youngest acceptable age for a given site's terms of service, avoiding "are you really from the future?" messages.

Define consistent relative times and dates for time inputs and elements. See moment.js for a great example of what a baseline API for this should look like.

Provide validation APIs more control and much less boilerplate when it comes to custom client-side validation. Define validation errors as attributes on input elements

Add the option to specify custom messages per ValidityState, including defining custom ones. Allow the developer to choose when validation occurs Default to inline validation with a 2 second debounce, and/or onBlur() Allow overriding CSS for browser validation error message popovers Introduce camera and microphone input types to make speech and media input easier. Explicitly separate value and add initialValue (see React.js's defaultValue attribute for why this makes sense). TL;DR: Check what happens when you set an initial value for an input that doesn't match a pattern. For example:

Mismatched value pattern

Eliminate the confusion created using scenarios such as the following: <input type=text inputmode=numeric> (triggers numeric software keyboard) vs. <input type="number"> (does not trigger numeric software keyboard)

<input type="number"> should automatically have inputmode="numeric" and pattern="[0-9]* " set, and the step UI should be explicitly enabled/disabled.

Don't force input elements to be wrapped by label elements to gain larger hit areas. Accessibility: yes! Markup hacks: no! <input type="emoji"> . With the number of "reaction" UIs cropping up, this only makes sense. Consider custom keyboards Sure, it seems scary, and lots of dangerous things could be done. All I'm asking is to consider it. There is also a lot of good that can be done as well.

What do you say, W3C?

These are just a few improvements and suggestions for better developer and user experience. Let's make them happen. Add your voice in support here: https://discourse.wicg.io/t/the-current-state-of-html-forms/1478