DEV Community

Cover image for Form inputs with React and Tailwind
Yogini Bende
Yogini Bende

Posted on • Edited on

Form inputs with React and Tailwind

Hello There,

TailwindCSS is a utility-first library which encourages to build everything using it's utility classes. With growing projects, we see the set of elements repeating themselves. With a utility-first approach, we not only repeat the list of classes but also the html structure of those elements. Best and well known way of handling this is creating components for mostly used elements.

In this article, I will be covering one mostly used component in any project - Form Input. Let’s dive in and understand how we can create it.

image

We will be creating an input element similar to the input shown in the image above. Let’s divide the whole process into 3 different parts -

  1. Styling the element.
  2. Handling error.
  3. Improving its usability.

1. Styling the element

If you notice, the input element has a label and border around both label and input. So let's take a wrapper div and put our label plus input element inside it. Hence the HTML look something like this -

const Input = (props) => {
    const { id, placeholder = '', label = '', type = 'text', ...rest } = props;
    return (
        <div>
            <label htmlFor={id}>{label}</label>
            <input type={type} id={id} placeholder={placeholder} {...rest} />
        </div>
    );
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

This is the simplest react component which will take id, label, type and placeholder as a prop and we have also added ...rest to maintain its flexibility.

Now, to add styling, the idea is, add a border to the outer div and place the label inside. Also, hide the border for the input element.

After adding Tailwind's utility classes this input will look like this -

const Input = (props) => {
    const { id, placeholder = '', label = '', type = 'text', ...rest } = props;
    return (
        <div
            className={`border transition duration-150 ease-in-out focus-within:border-primary border-gray-gray4`}
        >
            <label
                htmlFor={id}
                className={`text-xs text-primary font-light placeholder-gray-gray4 px-2 pt-1.5`}
            >
                {label}
            </label>
            <input
                type={type}
                className={`w-full px-2 pb-1.5 text-primary outline-none text-base font-light rounded-md`}
                id={id}
                placeholder={placeholder}
                {...rest}
            />
        </div>
    );
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

Notice how we have used focus-within property to change the border color after focusing input.

Till this point, we have already created a good looking input element. But, this element still have two issues -

  1. It will not show error
  2. If a user clicks on the box, outside the <input/> tag, the input will not be focused.

Let's solve these issues now.

2. Handling input error

To show input error efficiently, we will need to add two things, we will need to make the border red when error occurs and we will need to show error text below the input component.

Let's see the code first -

const Input = (props) => {
    const {
        id,
        wrapperClassName = '',
        placeholder = '',
        label = '',
        type = 'text',
        error = '',
        required = false,
        ...rest
    } = props;

    return (
        <div className={wrapperClassName}>
            <div
                className={`border transition duration-150 ease-in-out ${
                    error
                        ? 'focus-within:border-red border-red'
                        : 'focus-within:border-primary border-gray-gray4'
                }`}
            >
                <label
                    htmlFor={id}
                    className='text-xs text-primary font-light placeholder-gray-gray4 px-2 pt-1.5'
                >
                    {label} {required && <span className='text-red'>*</span>}
                </label>
                <input
                    type={type}
                    className='w-full px-2 pb-1.5 text-primary outline-none text-base font-light rounded-md'
                    id={id}
                    placeholder={placeholder}
                    {...rest}
                />
            </div>
            {error && <p className='text-xs pl-2    text-red mb-4'>{error}</p>}
        </div>
    );
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

Here, to add the error, we added a <p> tag in the bottom. As React needs only a single element wrapper, we added one more div outside. This div will be helpful to add margins or other styles to complete the input component.

We have also changed the border color conditionally for the outer component and added an asterisk, if the input is mandatory.

With this error handling, we have almost finished creating out component. One last thing pending here is, focusing our input when we click the outer div.

3. Improving usability

To focus our input on after clicking the outside div, we have useRef to our rescue😅. We added a ref to the input element inside our component. When we will click the outside div, we will add focus to the input using this ref.

Notice the onClick event we added to the outside div of input. This will solve all our requirements and a complete input component becomes ready.

Final version of our component will look something like this -

import { useRef } from 'react';

const Input = (props) => {
    const {
        id,
        wrapperClassName = '',
        placeholder = '',
        label = '',
        type = 'text',
        error = false,
        errorText = '',
        required = false,
        ...rest
    } = props;

    const inputRef = useRef();

    return (
        <div className={wrapperClassName}>
            <div
                className={`border transition duration-150 ease-in-out ${
                    error
                        ? 'focus-within:border-red border-red'
                        : 'focus-within:border-primary border-gray-gray4'
                }`}
                onClick={() => inputRef.current.focus()}
            >
                <label
                    htmlFor={id}
                    className='text-xs text-primary font-light placeholder-gray-gray4 px-2 pt-1.5'
                >
                    {label} {required && <span className='text-red'>*</span>}
                </label>
                <input
                    ref={inputRef}
                    type={type}
                    className='w-full px-2 pb-1.5 text-primary outline-none text-base font-light rounded-md'
                    id={id}
                    placeholder={placeholder}
                    {...rest}
                />
            </div>
            {errorText && (
                <p className='text-xs pl-2  text-red mb-4'>{errorText}</p>
            )}
        </div>
    );
};

export default Input;

Enter fullscreen mode Exit fullscreen mode

And that's it!

You can create many such components using Tailwind and React. I have used this code for my side project and I am creating more such components in my github repository.

Thank you so much for reading this article and always happy to receive your feedback. You can also connect with me on Twitter or buy me a coffee if you like my articles.

Thanks a lot! Keep learning 🙌

Top comments (3)

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

Nice article! Sweetly explained. 💯

Collapse
 
tanzimibthesam profile image
Tanzim Ibthesam

Something positive after seeing Tailwind getting bashed with React it really looks cool same for Vue. Thanks nice article

Collapse
 
hey_yogini profile image
Yogini Bende • Edited

Even I was fedup of reading Tailwind hate!
Its just a framework/tool, like it use it, don't like it move on 😅

And Thank you 😇