How to Style a Form With Tailwind CSS

Avatar of Nick Basile
Nick Basile on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

If you’ve been staying in the loop with the latest CSS frameworks, you’ve probably already heard of the newest kid on the block: Tailwind CSS. According to its documentation, “Tailwind is a utility-first CSS framework for rapidly building custom user interfaces.”

In practice, this means using a variety of classes that closely map to underlying CSS properties. For example, applying a class like .text-center to an element means that we’re setting its text-align property to center. Simple enough, right?

Using utility classes like this allows us to spend more time iterating through designs and avoiding premature abstraction of our CSS. Then, once we’re happy with our designs, Tailwind makes it easy to extract our utilities into component classes.

Now, I’m sure you know that even mentioning a utility framework is an open invitation for a certain amount of brouhaha. Before we start yelling at me on Twitter or the comments for even mentioning a utility framework, let’s just take a moment to remember that Tailwind is just one possible tool for us to use.

If you don’t want to add it to your toolbox, then no worries—you do you! But, if you’re interested in at least understanding this new tool, let’s take a look at building a sign-up form together.

Without further ado, let’s set up a fresh project with Tailwind, code out some HTML, and style it up.

The Set Up

Let’s start by creating a directory for us to work from. Using your terminal, navigate to the directory you’d like to create your project and run mkdir <your-project-name>. Now, let’s cd into our new project and follow the Tailwind installation guide.

Since we want to see everything that Tailwind can do, let’s install it with npm or Yarn using the following.

# Using npm
npm install tailwindcss --save-dev

# Using Yarn
yarn add tailwindcss --dev

With Tailwind installed, we can now generate the configuration file by running ./node_modules/.bin/tailwind init. This generates a tailwind.js file for us in the root of our project. In this file, we can adjust Tailwind’s default settings to our project’s needs. For this project, we shouldn’t have to change a thing.

Now, let’s create a CSS file where we can manage our own CSS and inject the Tailwind styles. To do that we can run touch styles.css from our project directory.

Inside of this new file, we can use Tailwind’s @tailwind directive to inject the preflight and utilities styles into our CSS. preflight contains all of the base styles and some browser style normalizations, while utilities adds all of the utility classes we specified in our config file. So, our styles.css file should look something like this:

@tailwind preflight;

/* Here we can add any custom overrides */

@tailwind utilities;

/* Here we can add our own utilities or custom CSS */

If you’re using PHPStorm and you’re annoyed by the red squiggles in your CSS file for @tailwind, just add /*noinspection CssInvalidAtRule*/ above where you use it.

With that all set up, we can run ./node_modules/.bin/tailwind build styles.css -o main.css to generate the CSS file we’d like to use in our project. This may seem a bit tedious, but don’t worry! Tailwind works with proper build tools like Webpack, Gulp, or Laravel Mix, so in a larger project you can just set it and forget it.

That’ll take care of our Tailwind set up! Now, we can start coding our HTML.

Our HTML

Before we can style our sign-up form, we need to build it! To start, we’ll need a simple index.html file. So, from your root directory, you can run touch index.html to create the file. Then, we can add the following snippet to get us started.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Tailwind Intro</title>
  <link rel="stylesheet" href="main.css">
</head>
<body>

</body>
</html>

As you can see, it’s your typical HTML page. The only wrinkle here is that we’re importing our main.css file and we’ve given our page a descriptive title. Now, let’s start building our sign-up form!

To start, let’s add two nested <div> elements to the inside of our <body> tag.

<body>
  <div>
    <div>
          
    </div>
  </div>
</body>

We’ll use the outer <div> for our page positioning, while the inner <div> will be the wrapper for our form. Now, inside the inner <div>, we can add a <h1> to label the form, and a <form>.

<div>
  <h1>Sign Up</h1>
  <form action="/" method="post">
              
  </form>
</div>

We’re really cooking with gas now! To finish the form, we just need to add the <label> elements, <input> elements, and <button>. As we add them, let’s wrap each of our <label> <input> pairs in a <div> so they stay together.

<form action="/" method="post">
  <div>
    <label for="first_name">First Name</label>
    <input type="text" name="first_name" id="first_name">
  </div>
  <div>
    <label for="last_name">Last Name</label>
    <input type="text" name="last_name" id="last_name">
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" name="email" id="email">
  </div>
  <div>
    <label for="password">Password</label>
    <input type="password" name="password" id="password">
  </div>
  <button type="submit">Create Account</button>
</form>

Finally, let’s add a link to access the login page right below our form.

<div>
  <!-- Form is here -->
  <a href="/login">Already have an account?</a>
</div>

Putting that all together, our HTML will look like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Tailwind Intro</title>
  <link rel="stylesheet" href="main.css">
</head>
<body>
  <div>
    <div>
      <h1>Sign Up</h1>
      <form action="/" method="post">
        <div>
          <label for="first_name">First Name</label>
          <input type="text" name="first_name" id="first_name">
        </div>
        <div><label for="last_name">Last Name</label>
          <input type="text" name="last_name" id="last_name">
        </div>
        <div>
          <label for="email">Email</label>
          <input type="email" name="email" id="email">
        </div>
        <div>
          <label for="password">Password</label>
          <input type="password" name="password" id="password">
        </div>
        <button type="submit">Create Account</button>
      </form>
      <a href="/login">Already have an account?</a>
    </div>
  </div>
</body>
</html>

Pretty straightforward, right? Now, when we see how that renders on the page, we should see something that looks like this:

Don’t be alarmed if it looks like the <input>s are missing; that’s just the browser resets at work. At last, we’re ready to see what this Tailwind CSS is all about.

Using Tailwind CSS

Being the good developers that we are, let’s take a mobile-first approach to styling our sign-up form. So, at a smaller viewport width of 400px, our page looks like this:

Styling Our Form Fields

Let’s start using Tailwind by styling our <input>s. First, let’s add a border so we can see it on the page. To do that, we just need to add the .border class. So, now our first name <input> will look like this:

<input class="border" type="text" name="first_name" id="first_name">

Now we can see it on the screen!

How easy was that? Let’s continue by adding some padding and making the text color a touch lighter. To do that, we just need to add the following classes: .py-2, .px-3, and .text-grey-darkest.

<input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">

With the first two classes, we’re taking advantage of the padding scale that comes with Tailwind and applying it vertically and horizontally to the element. If you want to define your own scale, just hop into your config file and change it to what you need. With the last class, we’re using Tailwind’s default color palette and changing the color of our element to the darkest grey.

Let’s take our form a step further. Now, we can position our <label>s above our <input>s and give them a bit of styling.

<div class="flex flex-col mb-4">
  <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
  <input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
</div>

Look at that, our first name field looks great! And the best part is that I really don’t have to explain what these classes are doing—they explain themselves! But just so we’re all on the same page, let me run through them quickly.

The outer <div> has its display property set to flexvia .flex and its flex-direction is set to column with .flex-col. Then it has a bit of margin on the bottom thanks to .mb-4.

Meanwhile, our <label> has a little less margin on the bottom thanks to .mb-2. The rest of the classes make our text uppercased, bold, large (1.125rem), and the darkest grey in our color palette.

Altogether, a pretty quick and easy way to style our fields! Now, let’s add these styles to the rest of our fields, style our button and link, and see what we’re working with.

<form class="mb-6" action="/" method="post">
  <div class="flex flex-col mb-4">
    <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
    <input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
  </div>
  <div class="flex flex-col mb-4">
    <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="last_name">Last Name</label>
    <input class="border py-2 px-3 text-grey-darkest" type="text" name="last_name" id="last_name">
  </div>
  <div class="flex flex-col mb-4">
    <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
    <input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
  </div>
  <div class="flex flex-col mb-6">
    <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
    <input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
  </div>
  <button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
</form>
<a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>

Adding Hover Styles

Now things are starting to look better! In this block of code, everything should be pretty self-explanatory. However, we have added one new thing: a state variant. If we take a look at our <button>, we can see one in action.

<button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>

If you look at the class right after .bg-teal, you can see that we’ve added a hover: prefix to .bg-teal-dark. These prefixes let us style elements on hover and focus, and they let us use breakpoints too! All in all, this is a pretty powerful feature of Tailwind and it lets us create dynamic, responsive UI very quickly.

Now, let’s wrap our mobile view by positioning our form in the middle of the screen and adding a colorful page background.

<div class="flex items-center h-screen w-full bg-teal-lighter">
  <div class="w-full bg-white rounded shadow-lg p-8 m-4">
    <h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
    <form class="mb-4" action="/" method="post">
      <div class="flex flex-col mb-4">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
        <input class="border py-2 px-3 text-grey-darkest" type="text" name="first_name" id="first_name">
      </div>
      <div class="flex flex-col mb-4">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="last_name">Last Name</label>
        <input class="border py-2 px-3 text-grey-darkest" type="text" name="last_name" id="last_name">
      </div>
      <div class="flex flex-col mb-4">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
        <input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
      </div>
      <div class="flex flex-col mb-6">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
        <input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
      </div>
      <button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
    </form>
    <a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>
  </div>
</div>

Bada bing bada boom, we’ve got ourselves a good-looking mobile sign-up form! But, what happens when we take a look at this on a bigger screen?

Responsive Design

It’s certainly better than our plain HTML, but it needs some work. Let’s use some of the responsive state variants and style this for larger screens.

<div class="flex items-center h-screen w-full bg-teal-lighter">
  <div class="w-full bg-white rounded shadow-lg p-8 m-4 md:max-w-sm md:mx-auto">
    <h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
    <form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
      <div class="flex flex-col mb-4 md:w-1/2">
        <label class="mb-2 uppercase tracking-wide font-bold text-lg text-grey-darkest" for="first_name">First Name</label>
        <input class="border py-2 px-3 text-grey-darkest md:mr-2" type="text" name="first_name" id="first_name">
      </div>
      <div class="flex flex-col mb-4 md:w-1/2">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest md:ml-2" for="last_name">Last Name</label>
        <input class="border py-2 px-3 text-grey-darkest md:ml-2" type="text" name="last_name" id="last_name">
      </div>
      <div class="flex flex-col mb-4 md:w-full">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="email">Email</label>
        <input class="border py-2 px-3 text-grey-darkest" type="email" name="email" id="email">
      </div>
      <div class="flex flex-col mb-6 md:w-full">
        <label class="mb-2 uppercase font-bold text-lg text-grey-darkest" for="password">Password</label>
        <input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">
      </div>
      <button class="block bg-teal hover:bg-teal-dark text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Create Account</button>
    </form>
    <a class="block w-full text-center no-underline text-sm text-grey-dark hover:text-grey-darker" href="/login">Already have an account?</a>
  </div>
</div>

Thanks to our responsive prefixes, our sign-up form is looking much better! Let’s take a look at our <form> for some examples.

<form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
  <!-- ... -->
</form>

Just like with our hover: prefix, we’re only applying the prefixed classes when that condition is met. In this case, that means we’re only applying the flex styles to our <form> when the page’s min-width is 768px.

Extracting Utilities Into Components

Now that we’ve finished prototyping our form, we can extract our utility classes into component classes. Let’s start by extracting our <input> classes.

<input class="border py-2 px-3 text-grey-darkest" type="password" name="password" id="password">

As we can see, our <input> has a couple of classes on it. We can extract these to our CSS using Tailwind’s @apply directive. @apply allows us to apply the same styles that our utility classes use to a new class. So, at the bottom of our styles.css file, we can add the following:

.field {
  @apply .border .py-2 .px-3 .text-grey-darkest;
}

Then, once we’ve re-complied our Tailwind files, our <input> can just have the .field class.

<input class="field" type="password" name="password" id="password">

As you can see, with Tailwind we get the best of utility and component classes! We can iterate quickly with utility classes, and still extract component classes when we start to see a pattern.

Even better, we can blend them to handle those one-off cases where dedicated component classes don’t make sense.

The Final CSS

Applying this thinking to the rest of our code, our CSS and HTML will look like this.

@tailwind preflight;

/* Here we can add any custom overrides */

.field {
  @apply .border .py-2 .px-3 .text-grey-darkest;
}

.field-label {
  @apply .uppercase .font-bold .text-lg .text-grey-darkest .mb-2;
}

.field-group {
  @apply .flex .flex-col;
}

.btn {
  @apply .block .text-white .uppercase .text-lg .p-4 .rounded;
}

.btn-teal {
  @apply .bg-teal;
}

.btn-teal:hover {
  @apply .bg-teal-dark;
}

.link {
  @apply .block .no-underline .text-sm;
}

.link-grey {
  @apply .text-grey-dark;
}

.link-grey:hover {
  @apply .text-grey-darker;
}

@tailwind utilities;

/* Here we can add our own utilities or custom CSS */

The Final HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Tailwind Intro</title>
  <link rel="stylesheet" href="main.css">
</head>
<body>
    <div class="flex items-center h-screen w-full bg-teal-lighter">
      <div class="w-full bg-white rounded shadow-lg p-8 m-4 md:max-w-sm md:mx-auto">
        <h1 class="block w-full text-center text-grey-darkest mb-6">Sign Up</h1>
        <form class="mb-4 md:flex md:flex-wrap md:justify-between" action="/" method="post">
          <div class="field-group mb-4 md:w-1/2">
            <label class="field-label" for="first_name">First Name</label>
            <input class="field md:mr-2" type="text" name="first_name" id="first_name">
          </div>
          <div class="field-group mb-4 md:w-1/2">
            <label class="field-label md:ml-2" for="last_name">Last Name</label>
            <input class="field md:ml-2" type="text" name="last_name" id="last_name">
          </div>
          <div class="field-group mb-4 md:w-full">
            <label class="field-label" for="email">Email</label>
            <input class="field" type="email" name="email" id="email">
          </div>
          <div class="field-group mb-6 md:w-full">
            <label class="field-label" for="password">Password</label>
            <input class="field" type="password" name="password" id="password">
          </div>
          <button class="btn btn-teal mx-auto" type="submit">Create Account</button>
        </form>
        <a class="link link-grey w-full text-center" href="/login">Already have an account?</a>
    </div>
  </div>
</body>
</html>

We’ve extracted our duplicate classes and left the rest. As we find ourselves building similar components, then we can extract more classes!

Wrap Up

Whew! We certainly covered a lot in this post. We started out stretching our HTML muscles by building a quick form, and then we took a mobile-first approach to our styles with Tailwind. Along the way, we saw how to use Tailwind’s utility classes to quickly style an element. Then, we used state variants to add some dynamic styles. And finally, we saw how we could have our cake and eat too when we extracted our duplicate classes to component classes.

I hope that you were able to get a taste of how Tailwind can make an impact in your own projects. If you’d like to mess around with this project, feel free to clone the repo and experiment with Tailwind yourself. As always, feel free to ask me any questions on Twitter. And until next time, happy coding!