splitforms.com
← Back to the journal

Change the 'Please fill out this field' Message (setCustomValidity)

Change the HTML5 Please fill out this field message with setCustomValidity and oninvalid, fix stuck custom messages, and build inline errors.

Start free — 500 submissionsSee pricing →No card, no time limit. Pro is $5 per month.
Short answer
Browsers show generic validation text such as "Please fill out this field" in the user's language. Override it with oninvalid and setCustomValidity(), then clear with setCustomValidity('') on input so the field is not stuck invalid. For branded UI, use novalidate plus your own error spans.
Try it on your own form

Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.

Create free account

Where "Please fill out this field" comes from

When a required control fails constraint validation, the browser blocks submit and shows a small bubble next to the field. The string is not in your HTML: the user agent generates it, often localized (for example French or Spanish equivalents). You cannot restyle that bubble with CSS or move it inside your layout.

What you can change is the message text via the Constraint Validation API. The broader validation flow (HTML attributes plus JavaScript) is covered in how to validate an HTML form with JavaScript. This article focuses on replacing the default tooltip and avoiding the bugs that make setCustomValidity feel broken.

Simple fix: oninvalid and oninput

The smallest working pattern sets a custom message when the field is invalid and clears it as soon as the user edits:

<input
  type="email"
  name="email"
  required
  oninvalid="this.setCustomValidity('Please enter your email')"
  oninput="this.setCustomValidity('')"
/>

On first failed submit, oninvalid runs and the bubble shows your text instead of the generic one. On every keystroke, oninput resets custom validity so the browser can re-evaluate native rules (required, type="email", and so on).

The stuck-message bug. Once you call setCustomValidity('anything'), the field stays in a custom-error state until you call setCustomValidity(''). If you only set the message and never clear it, the value can look fine but validity.valid stays false, so form.checkValidity() fails and the form never submits. That is the #1 reason developers search "setCustomValidity not working". Always pair invalid / oninvalid with input / oninput (or change) that clears the message.

The same pattern checks that two fields match: see confirm password validation in JavaScript.

If required alone is not blocking empty fields the way you expect, see HTML form required attribute not working for name attributes, disabled fields and nested forms.

Better JavaScript: one message per validity flag

Inline handlers get awkward when one field can fail for several reasons. Use listeners and branch on el.validity:

const email = document.querySelector("#email");

email.addEventListener("invalid", () => {
  const el = email;
  if (el.validity.valueMissing) {
    el.setCustomValidity("Please enter your email.");
  } else if (el.validity.typeMismatch) {
    el.setCustomValidity("Use a valid email address.");
  } else if (el.validity.tooShort) {
    el.setCustomValidity("At least 5 characters.");
  } else {
    el.setCustomValidity("This value is not allowed.");
  }
});

email.addEventListener("input", () => {
  email.setCustomValidity("");
});

Check valueMissing before typeMismatch or patternMismatch so the user sees the most helpful error first. Other flags you may need include patternMismatch, rangeUnderflow, rangeOverflow, and stepMismatch. Test edge cases with the email regex tester or password regex tester when pattern is involved.

Pattern fields and the title attribute

For type="text" inputs with a pattern, Chrome and Firefox append the title attribute text to the browser's validation message. Use a short hint in title and keep the regex itself in pattern. Full behavior and anchoring rules are on the HTML pattern attribute reference.

<input
  type="text"
  name="zip"
  pattern="[0-9]{5}"
  title="Five-digit US ZIP code"
  required
/>

Fully custom inline errors (novalidate)

When you need errors under the label, icons, or live regions, skip the native bubble: add novalidate on the <form>, run form.checkValidity() (or validate each control yourself), and render text in a <span> linked with aria-describedby while setting aria-invalid="true" on the input. Mark required fields in markup with required so validity.valueMissing still works even though the browser will not pop its own UI. For when to reveal those messages (blur vs every keystroke), see validate form fields on blur.

form.addEventListener("submit", (e) => {
  e.preventDefault();
  let firstInvalid = null;
  for (const el of form.querySelectorAll("input, select, textarea")) {
    const err = document.getElementById(el.id + "-error");
    if (!el.checkValidity()) {
      el.setAttribute("aria-invalid", "true");
      if (err) err.textContent = el.validationMessage;
      firstInvalid ??= el;
    } else {
      el.setAttribute("aria-invalid", "false");
      if (err) err.textContent = "";
    }
  }
  if (firstInvalid) {
    firstInvalid.focus();
    return;
  }
  form.submit(); // or fetch(...)
});

React: onInvalid and onChange

In React, use the same API on the synthetic event target:

<input
  type="email"
  required
  onInvalid={(e) => {
    e.currentTarget.setCustomValidity("Please enter your email.");
  }}
  onChange={(e) => {
    e.currentTarget.setCustomValidity("");
  }}
/>

Use e.currentTarget (not a stale ref) inside the handler. Controlled components still need the clear-on-change step; updating React state alone does not reset custom validity on the underlying DOM node.

Validate on the server too

Custom HTML5 messages improve UX only. Bots and crafted requests bypass the browser entirely. Keep server-side checks for every field, and use a hosted endpoint if you do not want to maintain mail or webhook plumbing. Sign up free for a splitforms endpoint, or paste your markup into the form validator to catch missing names and broken constraints before deploy.

FAQ

How do I change the Please fill out this field message?

Add an oninvalid handler (or an invalid event listener) that calls setCustomValidity with your text, and an oninput or input listener that calls setCustomValidity('') so the field can become valid again. Without clearing the custom message, the browser keeps treating the field as invalid even after the user fixes the value.

Why does my form not submit after setCustomValidity?

A non-empty custom validity message marks the field as invalid until you clear it with setCustomValidity(''). If you only set the message on invalid and never clear it on input, checkValidity() and form submission stay blocked. That is the most common reason people search setCustomValidity not working.

Can I style the HTML5 validation bubble?

No. The native tooltip is drawn by the browser UI, not your CSS. You can change the text with setCustomValidity, but not fonts, colors or placement. For full control, turn off native validation with novalidate and show errors in your own elements with aria-describedby and aria-invalid.

How do I translate validation messages?

The browser generates default strings like Please fill out this field in the user's language. To control wording or locale yourself, set custom messages with setCustomValidity or build inline errors after checkValidity(). Server-side validation messages should use your app's i18n layer independently of the browser.

References: HTML spec: Constraint Validation API and MDN: setCustomValidity().

Related articles

More practical guidance from tutorials.

Browse the journal →
Tutorials

How to Validate an HTML Form with JavaScript (2026 Tutorial)

Step-by-step tutorial on validating HTML forms with JavaScript in 2026. Covers HTML5 constra…

14 min readRead →
Tutorials

How to Remove the Arrows From a Number Input (CSS, Tailwind, React)

Hide the number input spin buttons with ::-webkit-inner-spin-button and appearance: textfiel…

7 min readRead →
Tutorials

How to Change the Autofill Background Color of an Input (CSS, Tailwind)

Remove Chrome's yellow autofill background with input:-webkit-autofill, inset box-shadow, -w…

7 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewContact Forms for Every FrameworkStart here →GuideContact Form for Next.jsRead →GuideContact Form for ReactRead →GuideContact Form for VueRead →ReferenceContact form guideOpen →

Building forms with ChatGPT, Claude, Cursor, or v0? Connect the native MCP server and give your agent a production form backend.

Explore the MCP server →

Give your form a production backend.

One endpoint adds delivery, spam filtering, storage, and integrations. Start with 500 free submissions — no card, no time limit.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe