Client-side Validations with Tailwind CSS

Nangialai Stoman
8 min readAug 5, 2023

The demo of this project is live here

Form validations are important if your web application has some sort of form that a user can submit data to a particular server. Before a user can submit data to the server, it’s important to ensure that the data in the form are correct before submitting it to the server or database.

There are 2 different types of form validations that every developer must understand. First, client-side validations, which are mostly about JavaScript in the browsers, and second, server-side validations, which means validating data on the server side or the backend of your application.

Client-side validations or frontend validations are a way to tell the user that the filled-out data are correct or wrong. However, client-side validations are not the only solution to the security of your form. Client-side validations can easily be turned off in the browser and that’s why you need the second layer of validation, which is server-side validations.

In this article, we will only look at client-side validations.

There are a number of ways to implement client-side validations, in this tutorial, we will be using Tailwind CSS which is an awesome frontend framework for CSS.

1. Create your project

Open your terminal and create a new project using Vite. I named it tailwind-form-validations but you can give whatever name you like. Also, we pass in the --template react flag as it tells Vite to create a React project.

npm create vite@latest tailwind-form-validations -- --template react

Once the project is created, navigate to it using the following command:

cd tailwind-form-validations

Then, install Tailwind CSS using this command:

npm install -D tailwindcss postcss autoprefixer

Next, create a Tailwind config file:

npx tailwindcss init -p

Next, replace the content section in your tailwind.config.js file with the following code:

content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],

Finally, add the Tailwind directives to your index.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

In addition, I have added extra packages for icons and Tailwind CSS forms. You can install them too if you wish to. Just copy and paste the following commands to your terminal:

npm install react-icons --save
npm install -D @tailwindcss/forms

Since you installed Tailwind CSS forms, make sure to add them to your Tailwind config file. Add it to the plugins section of your Tailwind config file like this:

plugins: [
require('@tailwindcss/forms'),
// ...
],

2. Create the components and data file

Create a Button.jsx component in the src/components directory and paste the following code in that file:

const Button = ({ title, icon, ariaLabel }) => {
return (
<button
aria-label={ariaLabel}
type="button"
className="flex w-full items-center justify-center space-x-4 rounded-md border border-gray-300 bg-gray-100 py-3 hover:border-purple-400 hover:bg-gray-200 focus:ring-2 focus:ring-purple-400 focus:ring-offset-1 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-purple-600 dark:hover:bg-gray-600"
>
{icon}
<p>{title}</p>
</button>
);
};

export default Button;

We created this Button component as we will reuse it in our form.

Next, create a countries.js file in your src/data directory and paste the following code in there:

export const countries = [
{ name: 'Afghanistan', code: 'AF' },
{ name: 'Åland Islands', code: 'AX' },
{ name: 'Albania', code: 'AL' },
{ name: 'Algeria', code: 'DZ' },
{ name: 'American Samoa', code: 'AS' },
{ name: 'AndorrA', code: 'AD' },
{ name: 'Angola', code: 'AO' }
..............
..............
];

The countries.js file is a long file that contains the list of all countries in the world. I just added the first few countries in our code, but you can get the complete JSON list of countries here and past it in your countries.js file.

3. Validation form

Replace the code in your App.jsx file with the following code:

import { useState } from 'react';
import { FiGithub, FiTwitter } from 'react-icons/fi';
import Button from './components/Button';
import { countries } from './data/countries';

function App() {
const [data, setData] = useState({
fullName: '',
email: '',
country: '',
password: ''
});

const handleRegistration = (e) => {
e.preventDefault();

console.log(data);
};

// Destructure data
const { ...allData } = data;

// Disable submit button until all fields are filled in
const canSubmit = [...Object.values(allData)].every(Boolean);

return (
<div className="flex min-h-screen items-center justify-center px-4">
<div className="flex w-full flex-col items-center py-10 sm:justify-center">
<div className="w-full max-w-sm rounded-md bg-white px-6 py-6 shadow-md dark:bg-gray-900 sm:rounded-lg">
<form
action=""
onSubmit={handleRegistration}
className="group"
>
<div>
<label
htmlFor="fullName"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
Full Name
</label>
<div className="flex flex-col items-start">
<input
type="text"
name="fullName"
placeholder="Full Name"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
pattern="[0-9a-zA-Z ]{6,}"
required
onChange={(e) => {
setData({
...data,
fullName: e.target.value
});
}}
/>
<span className="mt-1 hidden text-sm text-red-400">
Full name must be at least 6 characters long
</span>
</div>
</div>
<div className="mt-4">
<label
htmlFor="email"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
Email
</label>
<div className="flex flex-col items-start">
<input
type="email"
name="email"
placeholder="Email"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
autoComplete="off"
required
pattern="[a-z0-9._+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
onChange={(e) => {
setData({
...data,
email: e.target.value
});
}}
/>
<span className="mt-1 hidden text-sm text-red-400">
Please enter a valid email address.{' '}
</span>
</div>
</div>
<div className="mt-4">
<label
htmlFor="country"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
Select your country
</label>
<select
id="country"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500"
onChange={(e) => {
setData({
...data,
country: e.target.value
});
}}
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
>
{country.name}
</option>
))}
</select>
</div>

<div className="mt-4">
<label
htmlFor="password"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
Password
</label>
<div className="flex flex-col items-start">
<input
type="password"
name="password"
placeholder="Password"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
autoComplete="off"
required
pattern="[0-9a-zA-Z]{8,}"
onChange={(e) => {
setData({
...data,
password: e.target.value
});
}}
/>
<span className="mt-1 hidden text-sm text-red-400">
Password must be at least 8 characters.{' '}
</span>
</div>
</div>
<div className="mt-4">
<label
htmlFor="password_confirmation"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
Confirm Password
</label>
<div className="flex flex-col items-start">
<input
type="password"
name="password_confirmation"
placeholder="Confirm password"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 placeholder-gray-300 focus:border-purple-500 focus:ring-purple-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-purple-500 dark:focus:ring-purple-500 [&:not(:placeholder-shown):not(:focus):invalid~span]:block invalid:[&:not(:placeholder-shown):not(:focus)]:border-red-400 valid:[&:not(:placeholder-shown)]:border-green-500"
autoComplete="off"
required
pattern="[0-9a-zA-Z]{8,}"
onChange={(e) => {
setData({
...data,
password: e.target.value
});
}}
/>
<span className="mt-1 hidden text-sm text-red-400">
Password must be at least 8 characters.{' '}
</span>
</div>
</div>
<a
href="#"
className="pt-1 text-xs text-purple-600 hover:text-purple-800 hover:underline dark:text-purple-300 dark:hover:text-purple-100"
>
Forget Password?
</a>
<div className="mt-4 flex items-center">
<button
type="submit"
disabled={!canSubmit}
className="mt-2 w-full rounded-lg bg-purple-700 px-5 py-3 text-center text-sm font-medium text-white hover:bg-purple-600 focus:outline-none focus:ring-1 focus:ring-blue-300 disabled:cursor-not-allowed disabled:bg-gradient-to-br disabled:from-gray-100 disabled:to-gray-300 disabled:text-gray-400 group-invalid:pointer-events-none group-invalid:bg-gradient-to-br group-invalid:from-gray-100 group-invalid:to-gray-300 group-invalid:text-gray-400 group-invalid:opacity-70"
>
Create account
</button>
</div>
</form>
<div className="text-md mt-4 text-zinc-600 dark:text-zinc-300">
Already have an account?{' '}
<span>
<a
className="text-purple-600 hover:text-purple-800 hover:underline dark:text-purple-400 dark:hover:text-purple-100"
href="#"
>
Login instead
</a>
</span>
</div>
<div className="my-4 flex w-full items-center">
<hr className="my-8 h-px w-full border-0 bg-gray-200 dark:bg-gray-700" />
<p className="px-3 ">OR</p>
<hr className="my-8 h-px w-full border-0 bg-gray-200 dark:bg-gray-700" />
</div>
<div className="my-6 space-y-2">
<Button
title="Continue with Github"
ariaLabel="Continue with Github"
icon={<FiGithub className="text-xl" />}
/>
<Button
title="Continue with Twitter"
ariaLabel="Continue with Twitter"
icon={<FiTwitter className="text-xl" />}
/>
</div>
</div>
<div className="mt-6 flex items-center justify-center ">
<a
href="https://github.com/realstoman/tailwind-form-validations"
target="__blank"
className="cursor-pointer text-xl text-gray-700 underline hover:text-gray-900"
>
Github repo
</a>
</div>
</div>
</div>
);
}

export default App;

This is the actual form that has the validation rules in its input fields. Below is a quick explanation of the file:

  • We create a data state that's going to hold the form values
  • We destructure all data to allData using the spread syntax
  • Then, we create the canSubmit variable which makes sure that all the fields are filled in before submitting.
  • Every input field has the required validation classes from Tailwind CSS, an onChange method that keeps the updated input or selects the value, and also a pattern that’s built using regular expression.
  • Then we pass the !canSubmit which is going to toggle the form state to the disabled property in the button
  • Last but not least, we use the button component that we have created to continue with Github and Twitter

Tip: You can extract the input and select fields to separate reusable components to make the App.jsx file smaller.

The source code for this tutorial is available at my Github account here: https://github.com/realstoman/tailwind-form-validations

Conclusion

This article has covered the following:

  • Creating a React project with Vite
  • Adding Tailwind CSS to React
  • Creating reusable components
  • Form validations with Tailwind CSS

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Nangialai Stoman
Nangialai Stoman

Written by Nangialai Stoman

Programming, Philosophy, Design, AI

No responses yet

Write a response