Vivian Voss

The Native Form

html javascript web

Lean Web ■ Episode 7

“We need a validation library for forms.”

One encounters this sentence in sprint plannings, architecture reviews, and that peculiarly confident moment when a developer opens a pull request adding 44 kilobytes of JavaScript to a project that collects an email address and a password. The follow-up question is always the same, and it is never asked: for what, precisely?

The browser has validated email addresses since 2014. Not approximately. Not with a polyfill. Natively, in every browser that matters, with a single HTML attribute. The specification is HTML5 Constraint Validation, and it has been stable for over a decade. It was stable before React existed. It was stable before most npm packages had their first birthday, and it will be stable long after they have been abandoned.

The Cascade

Consider a React application. A form wants to submit. The browser raises a submit event, the way browsers have done since the late 1990s. React’s response? e.preventDefault(). Stop that nonsense. We will handle this ourselves.

And so the cascade begins. The form no longer submits natively, which means the browser’s built-in validation no longer fires, which means you need a validation library, which means you need a schema definition language, which means you need Formik. Formik needs a schema validator, so you add Yup. Yup needs schema definitions, so you write type-adjacent objects that describe the shape of data your HTML already described perfectly well with type="email" and required.

The modern alternative is React Hook Form with Zod. It is lighter (38 KB instead of 44 KB), better designed, and still entirely unnecessary for the problem it claims to solve. You are paying 38 kilobytes to re-implement what the browser does for zero kilobytes. The reduction from 44 to 38 is presented as progress. It is optimising the cost of a thing you did not need to buy.

What the Browser Already Does

The Constraint Validation API has been shipping in every major browser since 2014. It is not experimental. It is not behind a flag. It is not a proposal. It is the platform.

Validation. <input type="email" required>. One line. The browser checks the format, prevents submission if the field is empty, and displays a localised error message in the user’s language. No JavaScript. No schema. No library.

Custom error messages. input.setCustomValidity('Please use your work email'). One line. Native since 2014. The message appears in the browser’s built-in validation tooltip. No state management. No touched tracking. No onChange handler that sets an error string in a React state object that triggers a re-render that updates a <span> that was invisible until a boolean flipped.

Custom styling. input:invalid { border-color: crimson; }. A CSS pseudo-selector. The browser applies it when validation fails. No JavaScript state. No conditional class names. No className={errors.email ? 'error' : ''}. The newer :user-invalid pseudo-class is even better: it only activates after the user has actually interacted with the field, solving the “everything is red on page load” problem that form libraries spend considerable effort addressing.

Collecting form data. Object.fromEntries(new FormData(form)). One line. Returns a plain object with every named field’s value. No controlled components. No useState for every input. No synthetic event handlers. The form already knows its own data. It has always known its own data. You simply had to ask.

Form Validation: Bundle Cost Minified + gzipped transfer size Formik + Yup 44 KB React Hook Form + Zod 38 KB Native HTML5 0 KB The cheapest code is the code you do not ship.

The Real Problem

Form libraries do not solve form problems. They solve framework problems. React, Vue, and Angular declared war on browser primitives. They intercepted form submission. They replaced native state with synthetic state. They built virtual DOMs that do not know what an <input> contains unless you tell them on every keystroke. And then, having removed every native capability, they sold the replacements as features.

Formik exists because React broke forms. Yup exists because Formik needs validation that HTML already provided. Zod exists because TypeScript developers wanted type-safe schemas, which is reasonable, but TypeScript developers also wanted to validate HTML forms with those schemas, which is using a sledgehammer to install a picture hook.

The framework created the problem. The ecosystem sold the solution. The developer paid the bill. Twice: once in bundle size, once in complexity.

Two Paths to a Validated Form NATIVE HTML5 User submits form Browser validates natively new FormData(form) 3 steps. 0 KB. FRAMEWORK + LIBRARY e.preventDefault() useState per field Schema validation (Yup/Zod) Error state management Re-render with errors Collect values from state 6 steps. 38-44 KB. The browser is not your enemy. The browser is your runtime.

The Honest Exceptions

There are legitimate cases for form libraries. A multi-step wizard with conditional fields, server-side validation that must merge with client-side errors, forms with dozens of interdependent fields where custom validation logic genuinely exceeds what the Constraint Validation API offers comfortably. These cases exist. They are also not the reason most teams install Formik.

Most teams install Formik because the tutorial used Formik. Because the boilerplate project included it. Because someone on the team had used it at their previous company. Because the question “does the browser already do this?” was never asked. The answer, in the overwhelming majority of cases, is yes. The browser does this. The browser has done this for over a decade. The browser does it for free, in zero kilobytes, with localised error messages, with CSS pseudo-selectors for styling, and with an API that requires precisely one line of JavaScript to collect every field value.

The Runtime You Already Have

The browser is not a rendering target. It is not a viewport for your JavaScript application. It is a runtime with form handling, validation, state management (the DOM), styling hooks, accessibility features, and internationalisation. It has had these things for years. It will have them for years to come. They do not change between major versions. They do not require migration guides. They do not have supply chain vulnerabilities.

Every kilobyte of form validation JavaScript you ship is a kilobyte that says: “I did not know the browser could do this.” And in 2026, twelve years after the Constraint Validation API shipped in every major browser, that is no longer an explanation. It is an expense report that nobody approved.

<input type="email" required>. One attribute. Zero kilobytes. Native since 2014. The form was always there. You just had to stop preventing it.