State of React uncontrolled forms in 2022

The full form of this demo
The full form of this demo

Main foci


To provide basic accessibility, semantic HTML elements were used. Invalid states result in aria-messages which are connected to the input-elements through the correct attributes. In the accessibility section of the tutorial, they use aria-describedby. I actually think that aria-invalid combined with aria-errormessage is a stronger solution and prefer that. If anyone has better experiences in this regard, I would be very happy to receive feedback.

Usability with disabled JavaScript in the client

Isomorphic React with pre-rendered HTML gives a lot of power in our hands. But with that, I think, comes the responsibility to provide a good experience for users who don’t have client-side rendering (CSR). Still in 2022!
The form in this demo can be used without CSR. Of course, some features, like revealing password input, or better validation requires JavaScript in the client. However, this can be seen as a progressive improvement. When CSR is available, it adds attribute novalidate to the form and therefore disables basic HTML form validation. But even with disabled JavaScript in the client, submit will call the URL of action prop with the preferred method and still provide basic validation and a workable form.

Client-side validation

Client-side form validation is always a user-experience enhancement. In general, I combined HTML validation attributes plus custom JavaScript validation functions. The attributes provide basic validation for scenarios with disabled CSR.
Which rules result in HTML attributes are configured here (as a start). All other validation constraint will be handled with custom validator functions. They should therefore be available here. In this configuration, validation attributes have a higher priority compared to custom validator functions. For example if we decide to delete required from the htmlValidationAttributes, we should add a validator function with the signature of () => (value) => boolean and handle it in createValidator.

The properties of the ValidityState Interface are a bit wild in my opinion and not always attributable to the corresponding HTML validation attribute. But it basically says "yes, I am valid" or if not, it gives you a reason. I also started adding a mapping from the validation attributes to the property of the ValidityState.


Nowadays, it makes definitly sense to use accent-color. This gives you at least a little power to syle “hard-to-style” elements like input[type=checkbox] and align them with your brand style.
Also the combination of outline and outline-offset is really nice, since this is a very common visual requirement for focus states, and often needed some quirks to achieve that.


In the end, this is a very simple solution. But if you like this approach, using the browser’s capabilities with uncontrolled React form elements, React Hook Form might be worth to be checked out. Thanks for reading!



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Thomas Rutzer

Thomas Rutzer


hay I’m Thomas, specialized in crafting unique interfaces & interactions for the browser platform. Meet me on twitter or github: @thomasrutzer