Build an Angular login form with Tailwind CSS


Today we're going to build a login form with Angular and Tailwind CSS.

Tailwind CSS + Angular = 🥰

We'll be using a reactive Angular form.

And also be doing input validation to verify that the input we receive is valid. When input is invalid we'll display an error to the user.

login-form-angular-material

Let's get started!

1. Install Tailwind CSS

We can start with NPM to download and install the required Tailwind CSS packages.

npm install -D tailwindcss postcss autoprefixer

Then, after our dependencies are installed we'll run the tailwindcss command to create the tailwind.config.js file.

npx tailwindcss init

You'll now have a new file in your projects root directory to configure Tailwind CSS.

Since we're using Angular, we'll need to configure our template paths.

Like this.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{html,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

And last of all, add the @tailwind styles to our styles.scss file.

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

/* Other styles here */
h1 {
    @apply text-3xl font-bold
}

2. Build our login component with Tailwind CSS

We'll start by using the Angular CLI to generate a login component.

ng g c login

Then we'll open the login.component.ts file and add our imports, form and submit function.

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './login.component.html',
  styleUrl: './login.component.scss'
})
export class LoginComponent {

  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required]),
  });

  submit() {
    if (this.loginForm.invalid) {
      return;
    }

    const username = this.loginForm.get('username')?.value;
    const password = this.loginForm.get('password')?.value;

    // Authenticate with an API or Google Firebase Auth
  }
}

We've declared our form group and set up the validators.

Now on to our template's HTML code.

<div class="flex flex-col justify-center items-center bg-white">
    <div
        class="mx-auto flex w-full flex-col justify-center px-5 pt-0 md:h-[unset]">        
        <div
            class="my-auto mb-auto mt-8 flex flex-col md:mt-[70px] w-[350px] max-w-[450px] mx-auto md:max-w-[450px] lg:mt-[130px] lg:max-w-[450px]">
            <p class="font-bold text-center">Sign In</p>
            <div>
                <form class="mb-4" [formGroup]="loginForm" (ngSubmit)="submit()">
                    <div class="grid gap-2 mt-2">
                        <div class="grid gap-4">
                            <input 
                                class="mr-2.5 mb-2 h-full min-h-[44px] w-full rounded-lg border border-zinc-200 bg-white px-4 py-3 text-sm font-medium focus:outline-0 dark:border-zinc-800 dark:bg-transparent dark:text-white"
                                id="email" placeholder="name@example.com" type="email" autocapitalize="none"
                                autocomplete="email" autocorrect="off" name="email"
                                formControlName="email">
                            @if (loginForm.get('email')?.touched && loginForm.get('email')?.hasError('required')) {
                                <p class="text-sm ml-2 text-red-500">Email is required</p>
                            }                            
                            <input
                                id="password" placeholder="Password" type="password"
                                autocomplete="current-password"
                                class="mr-2.5 mb-2 h-full min-h-[44px] w-full rounded-lg border border-zinc-200 bg-white px-4 py-3 text-sm font-medium placeholder:text-zinc-400 focus:outline-0 dark:border-zinc-800 dark:bg-transparent dark:text-white dark:placeholder:text-zinc-400"
                                name="password"
                                formControlName="password">
                            @if (loginForm.get('password')?.touched && loginForm.get('password')?.hasError('required')) {
                                <p class="text-sm ml-2 text-red-500">Password is required</p>
                            }
                        </div>
                        <button class="whitespace-nowrap bg-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 mt-2 flex h-[unset] w-full items-center justify-center rounded-lg px-4 py-4 text-sm font-medium" [disabled]="loginForm.invalid" type="submit">Sign in</button>
                    </div>
                </form>
            </div>
        </div> 
    </div>
</div>

You'll notice that we're using the @if syntax to check for form errors and display an error for the email and password validation fields.

And here's the result!

login-form-angular-material

Till next time,

signature

Daniel Kreider