Client-side Validations with Tailwind CSS

Feb 15, 2023
--- views
JavaScript Newsletters - Banner Image

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 form data is correct.

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 validations, 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 you 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 paste 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 thats 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 select value and also a pattern that's built using regular expression.
  • Then we pass the !canSubmit variable, that 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 for the continue with Github and Twitter

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

The source code for this tutorial is available on 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