HTML forms come with built-in ways to validate form inputs and other controls against predefined rules such as making an input required, setting min and max constraints on range sliders, or establishing a pattern on an email input to check for proper formatting. Native HTML and browsers give us a lot of “free” features that don’t require fancy scripts to validate form submissions.
And if something doesn’t properly validate? We get “free” error messaging to display to the person using the form.
These are usually good enough to get the job done, but we may need to override these messages if we need more specific error content — especially if we need to handle translated content across browsers. Here’s how that works.
The Constraints API
The Constraints API is used to override the default HTML form validation messages and allows us to define our own error messages. Chris Ferdinandi even covered it here on CSS-Tricks in great detail.
In short, the Constraints API is designed to provide control over input elements. The API can be called at individual input elements or directly from the form element.
For example, let’s say this simple form input is what we’re working with:
<form id="myForm">
<label for="fullName">Full Name</label>
<input type="text" id="fullName" name="fullName" placeholder="Enter your full name" required>
<button id="btn" type="submit">Submit</button>
</form>
We can set our own error message by grabbing the <input>
element and calling the setCustomValidity()
method on it before passing it a custom message:
const fullNameInput = document.getElementById("fullName");
fullNameInput.setCustomValidity("This is a custom error message");
When the submit button is clicked, the specified message will show up in place of the default one.
Translating custom form validation messages
One major use case for customizing error messages is to better handle internationalization. There are two main ways we can approach this. There are other ways to accomplish this, but what I’m covering here is what I believe to be the most straightforward of the bunch.
Method 1: Leverage the browser’s language setting
The first method is using the browser language setting. We can get the language setting from the browser and then check whether or not we support that language. If we support the language, then we can return the translated message. And if we do not support that specific language, we provide a fallback response.
Continuing with the HTML from before, we’ll create a translation object to hold your preferred languages (within the script tags). In this case, the object supports English, Swahili, and Arabic.
const translations = {
en: {
required: "Please fill this",
email: "Please enter a valid email address",
},
sw: {
required: "Sehemu hii inahitajika",
email: "Tafadhali ingiza anwani sahihi ya barua pepe",
},
ar: {
required: "هذه الخانة مطلوبه",
email: "يرجى إدخال عنوان بريد إلكتروني صالح",
}
};
Next, we need to extract the object’s labels and match them against the browser’s language.
// the translations object
const supportedLangs = Object.keys(translations);
const getUserLang = () => {
// split to get the first part, browser is usually en-US
const browserLang = navigator.language.split('-')[0];
return supportedLangs.includes(browserLang) ? browserLang :'en';
};
// translated error messages
const errorMsgs = translations[getUserLang()];// form element
const form = document.getElementById("myForm");// button elementconst btn = document.getElementById("btn");// name input
const fullNameInput = document.getElementById("fullName");// wrapper for error messaging
const errorSpan = document.getElementById("error-span");
// when the button is clicked…
btn.addEventListener("click", function (event) { // if the name input is not there…
if (!fullNameInput.value) { // …throw an error
fullNameInput.setCustomValidity(errorMsgs.required); // set an .error class on the input for styling
fullNameInput.classList.add("error");
}
});
Here the getUserLang()
function does the comparison and returns the supported browser language or a fallback in English. Run the example and the custom error message should display when the button is clicked.
Method 2: Setting a preferred language in local storage
A second way to go about this is with user-defined language settings in localStorage
. In other words, we ask the person to first select their preferred language from a <select>
element containing selectable <option>
tags. Once a selection is made, we save their preference to localStorage
so we can reference it.
<label for="languageSelect">Choose Language:</label>
<select id="languageSelect">
<option value="en">English</option>
<option value="sw">Swahili</option>
<option value="ar">Arabic</option>
</select>
<form id="myForm">
<label for="fullName">Full Name</label>
<input type="text" id="fullName" name="fullName" placeholder="Enter your full name" required>
<span id="error-span"></span>
<button id="btn" type="submit">Submit</button>
</form>
With the <select>
in place, we can create a script that checks localStorage
and uses the saved preference to return a translated custom validation message:
// the <select> element
const languageSelect = document.getElementById("languageSelect");
// the <form> element
const form = document.getElementById("myForm");
// the button element
const btn = document.getElementById("btn");
// the name input
const fullNameInput = document.getElementById("fullName");
const errorSpan = document.getElementById("error-span");
// translated custom messages
const translations = {
en: {
required: "Please fill this",
email: "Please enter a valid email address",
},
sw: {
required: "Sehemu hii inahitajika",
email: "Tafadhali ingiza anwani sahihi ya barua pepe",
},
ar: {
required: "هذه الخانة مطلوبه",
email: "يرجى إدخال عنوان بريد إلكتروني صالح",
}
};
// the supported translations object
const supportedLangs = Object.keys(translations);
// get the language preferences from localStorage
const getUserLang = () => {
const savedLang = localStorage.getItem("preferredLanguage");
if (savedLang) return savedLang;
// provide a fallback message
const browserLang = navigator.language.split('-')[0];
return supportedLangs.includes(browserLang) ? browserLang : 'en';
};
// set initial language
languageSelect.value = getUserLang();
// update local storage when user selects a new language
languageSelect.addEventListener("change", () => {
localStorage.setItem("preferredLanguage", languageSelect.value);
});
// on button click
btn.addEventListener("click", function (event) {
// take the translations
const errorMsgs = translations[languageSelect.value];
// ...and if there is no value in the name input
if (!fullNameInput.value) {
// ...trigger the translated custom validation message
fullNameInput.setCustomValidity(errorMsgs.required);
// set an .error class on the input for styling
fullNameInput.classList.add("error");
}
});
The script sets the initial value to the currently selected option, saves that value to localStorage
, and then retrieves it from localStorage
as needed. Meanwhile, the script updates the selected option on every change event fired by the <select>
element, all the while maintaining the original fallback to ensure a good user experience.
If we open up DevTools, we’ll see that the person’s preferred value is available in localStorage
when a language preference is selected.
Wrapping up
And with that, we’re done! I hope this quick little tip helps out. I know I wish I had it a while back when I was figuring out how to use the Constraints API. It’s one of those things on the web you know is possible, but exactly how can be tough to find.
References
- Form Validation series (Chris Ferdinandi)
- Meet the Pseudo Class Selectors (Chris Coyier)
- Constraint validation (MDN)
- Client-side form validation (MDN)
Two Ways to Create Custom Translated Messaging for HTML Forms