This is the starting point of the series. You will create an Angular 19 project named angulartailwindtutorial1, add Tailwind CSS 4, and build a navbar, a hero section, and three routed pages. Later tutorials (the accessible contact form, NgRx state, and the Node API search) build on this project.

What you need

  • Node.js 20 or newer and npm (check with node -v and npm -v).
  • A code editor such as VS Code and a terminal.

1. Create the project

Use the Angular 19 CLI explicitly so the generated files match this tutorial, even if a newer CLI is installed globally.

npx @angular/cli@19 new angulartailwindtutorial1 --style=css --routing --ssr=false
cd angulartailwindtutorial1

The flags mean: --style=css uses plain CSS (Tailwind works best with it), --routing creates src/app/app.routes.ts, and --ssr=false skips server-side rendering to keep this 101 simple.

2. Run the app

npx ng serve

Open http://localhost:4200. The dev server rebuilds when you save a file. Stop it with Ctrl+C. To produce a production bundle, run npx ng build; the output goes to dist/.

3. Add Tailwind CSS 4

Tailwind 4 runs as a PostCSS plugin. Install it:

npm install tailwindcss @tailwindcss/postcss postcss

Create .postcssrc.json in the project root (next to package.json):

{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}

Replace the contents of src/styles.css with:

@import "tailwindcss";

Restart ng serve so Angular picks up the PostCSS configuration. Tailwind 4 needs no tailwind.config.js for a basic setup, and it scans your templates automatically.

4. Clear the starter page

Open src/app/app.component.html and replace everything with the navbar and the router outlet:

<app-navbar></app-navbar>
<router-outlet></router-outlet>

router-outlet is where Angular renders the component for the current URL.

5. Generate the components

npx ng generate component navbar
npx ng generate component hero
npx ng generate component about
npx ng generate component contact

In Angular 19 these are standalone components by default, and each file is named like navbar.component.ts. Note: if you follow along with Angular 20, the CLI drops the .component suffix (navbar.ts, class Navbar). The code below is otherwise the same.

6. Build the navbar

In src/app/navbar/navbar.component.ts, import RouterLink and RouterLinkActive so the template can use them:

import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-navbar',
  imports: [RouterLink, RouterLinkActive],
  templateUrl: './navbar.component.html',
})
export class NavbarComponent {}

Then src/app/navbar/navbar.component.html:

<nav class="bg-white shadow-md" aria-label="Main">
  <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
    <div class="flex h-16 items-center justify-between">
      <a routerLink="/" class="text-xl font-semibold text-blue-700 hover:text-blue-900">
        Surreal Scapes
      </a>
      <div class="flex gap-6">
        <a routerLink="/" routerLinkActive="underline" [routerLinkActiveOptions]="{ exact: true }"
           class="font-semibold text-gray-700 hover:text-blue-700">Home</a>
        <a routerLink="/about" routerLinkActive="underline"
           class="font-semibold text-gray-700 hover:text-blue-700">About</a>
        <a routerLink="/contact" routerLinkActive="underline"
           class="font-semibold text-gray-700 hover:text-blue-700">Contact</a>
      </div>
    </div>
  </div>
</nav>

Class breakdown: mx-auto max-w-7xl centers and limits the width; px-4 sm:px-6 lg:px-8 increases side padding at wider breakpoints; flex items-center justify-between puts the logo on the left and links on the right; hover: styles apply on mouse hover. aria-label="Main" names the navigation landmark for screen readers, and routerLinkActive underlines the current page so the active link is not shown by color alone.

7. Build the hero section

This version uses a CSS gradient instead of an image, so the project has no asset dependencies. In src/app/hero/hero.component.ts:

import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-hero',
  imports: [RouterLink],
  templateUrl: './hero.component.html',
})
export class HeroComponent {}

And src/app/hero/hero.component.html:

<section class="flex h-[70vh] items-center justify-center bg-gradient-to-br from-blue-900 to-slate-800 px-4 text-center text-white">
  <div>
    <h1 class="mb-4 text-4xl font-bold sm:text-5xl">Art Gallery</h1>
    <p class="mx-auto mb-6 max-w-2xl text-lg sm:text-xl">Explore exquisite art collections</p>
    <a routerLink="/contact"
       class="rounded-lg bg-white px-6 py-3 font-semibold text-blue-900 transition hover:bg-blue-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">
      Contact Us
    </a>
  </div>
</section>

h-[70vh] is an arbitrary value: 70 percent of the viewport height. The button uses dark text on a white background for strong contrast, and focus-visible: adds a clear outline for keyboard users.

8. Add the pages and routes

Give the About and Contact components simple content for now. In src/app/about/about.component.html:

<main class="mx-auto max-w-3xl px-4 py-10">
  <h1 class="mb-4 text-3xl font-bold">About</h1>
  <p class="text-gray-700">This gallery is a practice project for Angular and Tailwind CSS.</p>
</main>

In src/app/contact/contact.component.html:

<main class="mx-auto max-w-3xl px-4 py-10">
  <h1 class="mb-4 text-3xl font-bold">Contact</h1>
  <p class="text-gray-700">The contact form is built in the next tutorial.</p>
</main>

Now map URLs to components in src/app/app.routes.ts. The About page is lazy loaded, so its code is only downloaded when the user visits it:

import { Routes } from '@angular/router';
import { HeroComponent } from './hero/hero.component';
import { ContactComponent } from './contact/contact.component';

export const routes: Routes = [
  { path: '', component: HeroComponent },
  {
    path: 'about',
    loadComponent: () => import('./about/about.component').then((m) => m.AboutComponent),
  },
  { path: 'contact', component: ContactComponent },
];

Finally, make sure src/app/app.component.ts imports what its template uses:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './navbar/navbar.component';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, NavbarComponent],
  templateUrl: './app.component.html',
})
export class AppComponent {}

9. Verify

With npx ng serve running, check that the navbar appears on every page, the three links change the URL without a full reload, and the active link is underlined. Resize the browser to confirm the hero text stays readable. Run npx ng build to confirm there are no compile errors.

Common problems

  • Tailwind classes have no effect: confirm .postcssrc.json is in the project root and src/styles.css contains the @import, then restart ng serve.
  • “is not a known element” error: the component is missing from the imports array of the component that uses it.
  • Blank page on refresh at /about: this is a dev server or hosting rewrite issue; the Angular dev server already handles it, production hosts need a fallback to index.html.

Next in the series

Continue with the accessible contact form (Tailwind and template-driven forms), then move its submission state into NgRx, and finally connect a product search to a Node and Express API.