tailwind-icon

Integrating Tailwind into an Angular Frontend

Developing a modern Angular frontend requires a few essential tools: Node.js, the npm package manager, the Angular CLI, Tailwind CSS, and a text editor such as Visual Studio Code. This walkthrough outlines the process of creating a non-standalone Angular application with SCSS styling and routing enabled, followed by the integration of Tailwind and Bootstrap through the npm package manager. Configuration steps for both frameworks will be detailed, along with the generation of four components—one for a navbar that links to three additional component pages. The process will also cover dependency setup in app.module.ts, route definitions in app.routing.module.ts, and the implementation of the navbar and homepage components demonstrating the use of Tailwind. Setting up communication between the frontend and the backend will be covered in the next article. The first step begins with installing and configuring the required dependencies.

Configuring the Dependencies

ng new frontend --standalone=false --routing=true --style=scss
cd frontend
npm install
npm install -D tailwindcss
npm install -D @tailwindcss/postcss
npm install bootstrap
npm install @ng-bootstrap/ng-bootstrap@^18

The frontend application is initialized with Angular CLI using the ng new command. The project is created with non-standalone configuration, SCSS as the stylesheet format, and routing enabled. Using the non-standalone (--standalone=false) configuration ensures the project relies on a central AppModule for dependency management, while enabling routing (--routing=true) creates a dedicated AppRoutingModule to handle navigation between components. After navigating into the new project directory (cd frontend), the default Angular dependencies are installed with npm install.To add styling frameworks, Tailwind CSS is installed as a development dependency along with the PostCSS plugin required for Angular integration using the -D flag, which tells npm to install the dependency as a development dependency. Placing tailwind in the development dependencies is all that is required since the final runtime image does not actually use tailwind directly. Bootstrap is added as a core dependency, and @ng-bootstrap/ng-bootstrap is included to provide Angular-specific Bootstrap components compatible with Angular 19, which is the version of Angular used in this project. These installations establish the foundation for both utility-first and component-based styling within the project.

Configuring BootStrap & Tailwind CSS

This section wires Tailwind and Bootstrap into the Angular build so utility classes and component styles compile reliably and in the right order. The PostCSS plugin activates Tailwind’s processor, the Tailwind config scopes class scanning to the app, a dedicated tailwind.css entry file triggers Tailwind’s layers, and angular.json registers global styles with a precedence that prevents overrides from fighting. Without these steps, the styles would not be applied and the site would not render correctly.

frontend/.postcssrc.json

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

The PostCSS configuration file ensures that Tailwind CSS is properly integrated into the Angular build process. By specifying @tailwindcss/postcss as a plugin, Angular’s CLI compiles and processes Tailwind’s utility classes during the build, allowing the framework’s styles to be applied consistently across components. Without this configuration, Tailwind’s directives would not be recognized, and its styles would not be generated.

frontend/tailwind.config.js

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

The content glob points Tailwind’s scanner at all templates and TypeScript files under src, ensuring unused utilities are purged and referenced classes are emitted. The theme.extend block is left open for future tokens (colors, spacing, fonts), and plugins remains empty until additional Tailwind plugins are required. The array will eventually include various plugins, such as @tailwindcss/typography for markdown rendering. This configuration allows Tailwind to be used flexibly, with additional plugins being added as needed.

frontend/src/tailwind.css

/* frontend/src/tailwind.css */
@config "../tailwind.config.js";

/* Load Tailwind v4 */
@import "tailwindcss";

This file serves as the Tailwind entrypoint. The @config directive binds the stylesheet to the project’s tailwind.config.js, and @import "tailwindcss"; pulls in Tailwind’s base, components, and utilities layers so classes are available across the app.

frontend/angular.json

"styles": [
    "src/tailwind.css", // goes first
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "src/styles.scss"
]

Placing src/tailwind.css first ensures Tailwind’s base and utilities load before other styles. Including node_modules/bootstrap/dist/css/bootstrap.min.css next provides Bootstrap’s component styling, and keeping src/styles.scss last allows project-specific overrides to take precedence where necessary.

Check Tailwind

After setting up the dependencies, a quick check can be run to ensure Tailwind is working correctly by adding a few lines of code to the app.component.html file under the frontend/src/app/ directory.

frontend/src/app/app.component.html

<p>Tailwind v4 OK</p>
<p class="text-3xl font-bold underline p-4">Tailwind v4 OK</p>

The two paragraphs elements above include the same text, "Tailwind v4 OK" with different styling. Once added to the project, the application can be run in the terminal from the frontend/ directory with the following CLI command: ng serve --open. The --open option opens the application in the default browser. If Tailwind is working correctly, the two paragraphs should appear with different styling. If it is not, check the configuration files and ensure the dependencies are installed correctly. The versions of node, npm, and Angular CLI can all be checked with the output from the CLI command ng --version.

Routing and bootstrapping the Angular Application

There are four main components that need to be built: the Navbar Component, the Homepage Component, the Courses Component, and the Projects Component. The Navbar component is the component that will be displayed on every page of the website, providing navigation to the other three chief components across the current web design. The following section first generates all four components so that when the code for the navbar component is written, the three main parent components of the portfolio website can be easily referenced and routed to. Once generated, the dependencies for each component are bootstrapped in the app.module.ts file and the routes are defined in app.routing.module.ts.

ng generate component components/navbar
ng generate component components/homepage
ng generate component components/courses
ng generate component components/projects

The ng generate component command scaffolds new Angular components by automatically creating the required TypeScript, HTML, SCSS, and testing files within the frontend/src/app/components/ directory, while also updating the project’s module to include the new component. This automation not only ensures consistency in file structure and naming conventions but also reduces manual setup, allowing development to focus directly on implementing functionality and styling. With the necessary components now created, the next step is to register them properly within the application, which is handled in the section “Bootstrapping the Dependencies.”

Bootstrapping the Dependencies

frontend/src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { HttpClientModule, provideHttpClient, withFetch } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

import { NavbarComponent } from './components/navbar/navbar.component';
import { HomepageComponent } from './components/homepage/homepage.component';
import { CoursesComponent } from './components/courses/courses.component';
import { ProjectsComponent } from './components/projects/projects.component';

@NgModule({
  declarations: [
    AppComponent,
    NavbarComponent,
    HomepageComponent,
    CoursesComponent,
    ProjectsComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NgbModule,
    HttpClientModule
  ],
  providers: [
    provideClientHydration(withEventReplay()),
    provideHttpClient(withFetch())
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

The root module, app.module.ts, is responsible for organizing and registering the application’s building blocks. Each of the generated components—Navbar, Homepage, Courses, and Projects—are added to the declarations array, making them available for use throughout the application. Core Angular modules such as BrowserModule and HttpClientModule are imported alongside AppRoutingModule for navigation and NgbModule to provide Angular-compatible Bootstrap components. The providers array enables hydration with event replay (provideClientHydration(withEventReplay())) and configures the Angular HttpClient to use the modern fetch API (provideHttpClient(withFetch())). Finally, the bootstrap array ensures the application starts with all the root AppComponent bootstrapped into the Document Object Model (DOM). With these dependencies in place, the next step is defining the routes that connect the newly created components, as described in “Building the Routes.”

Building the Routes

app.routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoursesComponent } from './components/courses/courses.component';
import { ProjectsComponent } from './components/projects/projects.component';
import { HomepageComponent } from './components/homepage/homepage.component';

const routes: Routes = [
  { path: '', component: HomepageComponent }, 
  { path: 'homepage', component: HomepageComponent },
  { path: 'courses', component: CoursesComponent },
  { path: 'projects', component: ProjectsComponent },
  { path: '**', redirectTo: ''} 
];

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    anchorScrolling: 'enabled',
    scrollPositionRestoration: 'enabled',
    onSameUrlNavigation: 'reload',
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Routing defines how the application responds to navigation requests by mapping specific URL paths to Angular components. In this configuration, the default route ('') and the explicit /homepage path both render the HomepageComponent, while /courses and /projects direct to their respective components. An optional catch-all ('**') redirects unrecognized paths back to the homepage, ensuring graceful fallback behavior. The RouterModule.forRoot method enables routing across the application and is configured with options that enhance navigation: anchorScrolling allows fragment links to scroll to element anchors, scrollPositionRestoration returns the view to the previous scroll position when navigating back, and onSameUrlNavigation: 'reload' ensures components reload even when navigating to the same route. With routing in place, the next step is to implement the visual structure, beginning with the Navbar component that ties these routes together.

Building the Global Views

frontend/src/app/app.component.html

<div class="bg-gray-25 min-h-screen w-full ">
    <app-navbar></app-navbar>
    <div class="mt-3 mb-4 font-serif max-w-4xl mx-auto">
        <router-outlet></router-outlet>
    </div>
</div>

The root template, app.component.html, defines the global structure that persists across all routes. A container styled with Tailwind classes ensures the application spans the full width and height of the viewport (min-h-screen w-full) while applying a neutral background (bg-gray-25). The <app-navbar> component is positioned at the top, allowing navigation to remain consistent throughout the site. Below the navbar, a centered container (mx-auto) with controlled width (mt-3 mb-4 max-w-4xl) and serif font styling (font-serif) houses the router-outlet, which dynamically renders the active component based on the current route. This layout establishes a cohesive framework for the portfolio’s pages while keeping navigation fixed and content modular.

Implementing the Navbar and Homepage Components

The Navbar and Homepage components establish the foundation for both navigation and content presentation within the application. Together, they demonstrate how Angular components can be connected through routing while styled with Tailwind CSS to achieve a clean and responsive layout. The Navbar component provides links to the primary sections of the site, adapting seamlessly between desktop and mobile displays, while the Homepage component introduces the portfolio’s purpose with styled text and structure. This section highlights the integration of routing logic and Tailwind classes, offering a practical example of how functionality and design work in tandem.

The NavbarComponent provides the central navigation framework for the portfolio, linking to the homepage, courses, and projects pages. Built with Angular’s component structure, it combines TypeScript logic for routing and menu toggling with Tailwind CSS classes for responsive styling. The design includes a desktop layout with navigation links and a mobile-friendly hamburger menu that expands into a collapsible menu, ensuring accessibility across different screen sizes. This implementation demonstrates how Angular’s routing and event handling can be paired with Tailwind utilities to create an adaptive and visually consistent navigation bar.

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

@Component({
  selector: 'app-navbar',
  standalone: false,
  templateUrl: './navbar.component.html',
  styleUrl: './navbar.component.scss'
})
export class NavbarComponent {

  mobileMenuOpen = false;
  paths: string[] = ['Home', 'Projects', 'Courses'];

  constructor(private router: Router) {}

  toggleMobileMenu() {
    this.mobileMenuOpen = !this.mobileMenuOpen;
  }

  navigateTo(path: string) {
    this.router.navigate(['/', path.toLowerCase()]);
  }

}

The TypeScript file for the class NavbarComponent defines its structure and interactive behavior. A paths array stores the names of the main routes, simplifying navigation link generation in the template. The mobileMenuOpen property is used as a toggle flag to control whether the mobile navigation menu is visible. Two methods handle user interaction: toggleMobileMenu() switches the mobile menu between open and closed states, and navigateTo(path: string) uses Angular’s Router service to programmatically navigate to the selected route. Together, these properties and methods provide the logic that powers both the responsive desktop and mobile navigation experiences.

<nav id="navbar" class="bg-gray-800 font-serif !shadow-md !shadow-neutral-400">
    <div class="max-w-7xl mx-auto sm:px-8 lg:px-10 py-2 flex justify-between text-xl">
        <!-- MH Logo -->
        <div id="mh-logo" role="button" (click)="navigateTo('home')" class="!text-white text-lg font-bold hover:!text-gray-300 px-2 py-2" style="text-decoration: none;">
            <svg class="h-8 w-8 overflow-visible" fill="none" viewBox="0 0 30 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" >
                <path d="M4 20 V4 L11 12 L18 4 V20 M30 12 H18 M30 4 V20" />
            </svg>
        </div>
        <div id="hamburger-icon"  role="button" (click)="toggleMobileMenu()" class="md:hidden flex text-white hover:!text-gray-300 focus:outline-none p-2">
            <svg class="h-8 w-8 mx-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6h16M4 12h16M4 18h16" />
            </svg>
        </div>
        <div id="desktop-navigation-links" class="hidden md:flex flex-1 justify-end space-x-10 mt-3">
            <div *ngFor="let path of paths">
                <a id="{{path.toLowerCase()}}-link" (click)="navigateTo(path)" role="button" class="text-white rounded-md font-bold hover:!text-gray-300"
                style="text-decoration: none;">{{ path }}</a>
            </div>
        </div>
    </div>
    <div id="mobile-menu-wrapper" class="overflow-hidden transition-all duration-400 ease-in-out origin-top" 
        [ngClass]="{'max-h-0': !mobileMenuOpen, 'max-h-96': mobileMenuOpen}">
        <div id="mobile-menu" class="
            md:hidden px-3 pb-3 space-y-2 text-center font-bold
            transition-transform duration-200 ease-in-out origin-top scale-y-0"
            [ngClass]="{'scale-y-0': !mobileMenuOpen, 'scale-y-100': mobileMenuOpen}">
            <div *ngFor="let path of paths">
                <a id="{{path.toLowerCase()}}-link" (click)="navigateTo(path)" role="button" class="text-white hover:!text-blue-600" style="text-decoration: none;">{{ path }}</a>
            </div>
        </div>
    </div>
</nav>

The outer <nav> wrapper defines the navigation bar and applies a set of Tailwind utility classes to establish its appearance. The bg-gray-800 class gives the navbar a dark gray background with a slight navy blue tint, while font-serif ensures all text within the element inherits a serif typeface. The !shadow-md utility enforces a medium drop shadow with high priority, providing depth against the page background, and !shadow-neutral-400 overrides the shadow color to a neutral gray tone. Structurally, this <nav> element contains two major inner wrappers: the first is a flexible container that holds the logo, the desktop navigation links, and the hamburger icon for mobile screens; the second is a collapsible container that appears only on smaller devices, housing the vertically stacked mobile navigation links. This layout ensures that navigation adapts seamlessly to different screen sizes, with desktop links displayed in a row and mobile links revealed through an expanding menu.

<div id="mh-svg-logo" role="button" (click)="navigateTo('home')" class="!text-white text-lg font-bold hover:!text-gray-300 px-2 py-2" style="text-decoration: none;">
    <svg class="h-8 w-8 overflow-visible" fill="none" viewBox="0 0 30 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" >
        <path d="M4 20 V4 L11 12 L18 4 V20 M30 12 H18 M30 4 V20" />
    </svg>
</div>

The first inner wrapper <div> with the id="mh-svg-logo" functions as a clickable logo element that navigates to the homepage when selected. The Tailwind classes define its style and interaction: !text-white forces the text and child SVG strokes to render in white with high priority, text-lg adjusts the font size for consistency in alignment with other navigation items, font-bold ensures strong visual weight, hover:!text-gray-300 applies a lighter gray on hover, and px-2 py-2 add horizontal and vertical padding to increase clickable space. The role="button" attribute improves accessibility by identifying it as an interactive element and ensures the entire element is clickable, while (click)="navigateTo('home')" wires it to the Angular routing function.

Inside, the <svg> element renders a custom vector logo. Its viewBox="0 0 30 24" defines a coordinate system where the top-left corner is (0, 0) and the bottom-right corner is (30, 24) with the axes otherwise being in typical (x,y) coordinates. The class="h-8 w-10" sets the logo to a 10x8 Tailwind sizing scale, while overflow-visible ensures strokes extending beyond the box remain visible even when drawn outside the box. The fill="none" attribute disables interior fills, making only strokes visible, while stroke="currentColor" inherits the white text color set on the parent. Rounded edges for stroke endpoints and corners are defined by stroke-linecap="round" and stroke-linejoin="round", and stroke-width="2.5" sets the thickness.

The <path> element defines the logo’s actual shape using a series of drawing commands:

  • M4 20 V4 moves to coordinate (4, 20) and draws a vertical line (V) up to (4, 4).
  • L11 12 draws a straight line (L) from (4, 4) to (11, 12).
  • L18 4 draws another line from (11, 12) to (18, 4).
  • V20 continues vertically from (18, 4) down to (18, 20).
  • M30 12 H18 moves to (30, 12) and draws a horizontal line (H) back to (18, 12).
  • M30 4 V20 moves to (30, 4) and draws a vertical line down to (30, 20).

Together, these commands create a bold, geometric mark resembling a stylized letterform of my initials "MH". It is constructed entirely from strokes aligned to the SVG coordinate grid. This is just an example of a how a simple letterform logo might be created with SVG and placed in the top left corner of a website. If ever deploying a website, choosing something appropriate to the domain name or brand associated with the domain is usually a good idea.

<div id="hamburger-svg-icon"  role="button" (click)="toggleMobileMenu()" class="md:hidden flex text-white hover:!text-gray-300 focus:outline-none p-2">
    <svg class="h-8 w-10 mx-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6h16M4 12h16M4 18h16" />  
    </svg>
</div>

The <div> with id="hamburger-svg-icon" provides the clickable container for the hamburger menu, which toggles the visibility of the mobile navigation links. The Tailwind classes define its behavior and style: md:hidden hides this element on medium-sized screens and above, ensuring it only appears on smaller devices. flex applies flexbox layout, centering its child SVG vertically and horizontally within the container. text-white renders the icon strokes in white, while hover:!text-gray-300 changes the stroke color to a lighter gray on hover. focus:outline-none removes the default focus ring to provide a cleaner look, and p-2 applies padding around the icon, enlarging the interactive area for better accessibility. The role="button" attribute signals interactivity for screen readers, and (click)="toggleMobileMenu()" binds the click event to the Angular method that expands or collapses the mobile menu.

The child <svg> defines the three-bar hamburger icon. Its class="h-8 w-8 mx-1" sets the height and width to Tailwind’s 8-unit size and applies minimal horizontal margin. The fill="none" attribute disables any fill so only strokes are visible, while stroke="currentColor" ensures the lines inherit the white color defined on the parent container. The coordinate grid is established with viewBox="0 0 24 24", giving a 24×24 unit drawing area.

The <path> element uses three separate drawing commands to create the stacked horizontal bars:

  • M4 6h16 moves to (4, 6) and draws a horizontal line (h) 16 units long, ending at (20, 6).
  • M4 12h16 moves to (4, 12) and draws a second horizontal line 16 units long, ending at (20, 12).
  • M4 18h16 moves to (4, 18) and draws a third horizontal line 16 units long, ending at (20, 18).

Together, these three strokes form the classic hamburger icon, a universal symbol for mobile navigation menus.

<div id="desktop-navigation-links" class="hidden md:flex flex-1 justify-end space-x-10 mt-3">
    <div *ngFor="let path of paths">
        <div id="{{path.toLowerCase()}}-link" (click)="navigateTo(path)" role="button" class="text-white rounded-md font-bold hover:!text-gray-300"
        style="text-decoration: none;">{{ path }}</div>
    </div>
</div>

The <div> with id="desktop-navigation-links" serves as the container for the navigation bar’s desktop view, appearing only on medium-sized screens and larger. The class hidden hides it on small devices, while md:flex activates flexbox layout at the medium breakpoint, allowing the links to align horizontally. The flex-1 utility ensures the container stretches to fill available horizontal space, and justify-end pushes the links to the right side of the screen. Spacing between the items, in this case the links to the various pages on the website, is handled by space-x-10, which evenly separates each link, while mt-3 adds a slight top margin to align the links visually with the logo. Inside the container, Angular’s *ngFor directive iterates over the paths array from the component’s TypeScript file, creating a new link for each route. Each generated link is represented by a <div> whose id is dynamically constructed from the path name (for example, “home-link” or “projects-link”), allowing for unique identification. These links are styled with Tailwind utilities to appear bold (font-bold), white (text-white)), and slightly rounded at the corners (rounded-md), with a hover state (hover:) that shifts the text color to a lighter gray (!text-gray-300) for visual feedback. The role="button" attribute signals interactivity, giving it the same functionality as a button element, while the (click)="navigateTo(path)" event binding ties the element to Angular’s routing logic. An inline style ensures text decorations like underlines are suppressed (style="text-decoration: none;"), giving each link the appearance of a clean, button-like navigation element. Together, this structure produces a responsive, right-aligned navigation bar that is dynamically generated and styled to integrate seamlessly with the overall layout.

<div id="mobile-menu-wrapper" class="overflow-hidden transition-all duration-400 ease-in-out origin-top" 
    [ngClass]="{'max-h-0': !mobileMenuOpen, 'max-h-96': mobileMenuOpen}">
  ...
</div> 

The #mobile-menu-wrapper <div> acts as the collapsible container for the mobile navigation, and its visibility is driven by Angular’s ngClass directive. The ngClass selector is an Angular template directive (provided by CommonModule, which is re-exported by BrowserModule and thus available in this app) that conditionally applies CSS classes based on the value of the mobileMenuOpen property. In this binding, the boolean mobileMenuOpen, toggled in the component's TypeScript, selects between two class sets. When mobileMenuOpen is false, the wrapper receives max-h-0, constraining its height to zero and hiding the content. When mobileMenuOpen becomes true, the class set switches to max-h-96, expanding its height to reveal the content. The always-on base classes, overflow-hidden transition-all duration-400 ease-in-out origin-top, ensure any overflowing content is clipped during the animation, animate all changed properties, run the transition over 400ms with a smooth ease-in-out curve, and set the transform/height animation’s origin to the top edge so the panel appears to unfold downward. Using max-h-* instead of height enables a smooth height transition as content is revealed or concealed, while the opacity shift coordinates a fade effect with the size change for a polished open/close animation. This first wrapper supports the rise and fall of the mobile menu's background while the next wrapper supports the rise and fall of the mobile menu's content, including the links with their text.

<div id="mobile-menu" class="
    md:hidden px-3 pb-3 space-y-2 text-center font-bold
    transition-transform duration-200 ease-in-out origin-top scale-y-0"
    [ngClass]="{'scale-y-0': !mobileMenuOpen, 'scale-y-100': mobileMenuOpen}">
    <div *ngFor="let path of paths">
        <div id="{{path.toLowerCase()}}-link" (click)="navigateTo(path)" role="button" class="text-white hover:!text-blue-600" style="text-decoration: none;">{{ path }}</div>
    </div>
</div>

The #mobile-menu wrapper <div> defines the visible content of the collapsible mobile panel and layers two groups of Tailwind utilities to control layout and animation. The first group, hides the block on medium screens and above (md:hidden), adds horizontal and bottom padding (px-3 pb-3) to create breathing room around the links, inserts vertical rhythm between each generated item (space-y-2), centers the link text (text-center), and increases weight for stronger emphasis (font-bold). The second group, prepares the element for a smooth expand/collapse: the transition targets transforms (transition-transform), runs over 200ms with an ease-in-out curve (duration-200 ease-in-out), and sets the transform origin to the top edge so the menu appears to unfold downward (origin-top). Angular’s ngClass directive then toggles the transform scale based on the mobileMenuOpen boolean. When mobileMenuOpen is false, the wrapper retains scale-y-0, keeping the content vertically collapsed and effectively hidden. When mobileMenuOpen becomes true, scale-y-100 is applied, scaling the element to its full height and revealing the menu. Because the transition is limited to transforms, the open/close feels responsive and GPU-friendly, avoiding layout thrash. Inside the wrapper, an *ngFor structural directive iterates over the paths array defined in the component class, emitting one child wrapper per route label. Each child <div> receives an id derived from the lowercase path name (for example, home-link, projects-link, courses-link) to provide stable hooks for styling or testing, and binds a (click) handler that triggers navigateTo(path). The text-white and hover:!text-blue-600 utilities establish a clear default foreground color and a hover-state accent, while role="button" communicates interactivity despite using a <div> instead of an anchor element. The style="text-decoration: none;" ensures that no underline appears underneath the text for the navigation links. This could also be achieved with the !no-underline Tailwind class selector that overrides any conflicting instructions with an important flag (!).

This section assembled a responsive navigation bar that combines Angular routing logic with Tailwind CSS styling and transitions. The NavbarComponent defines state and handlers for route navigation and mobile menu toggling, while the template renders an SVG logo, a desktop link row, and a collapsible mobile panel. Tailwind utilities handle layout, spacing, and color, and ngClass conditionally applies animation classes to smoothly expand and collapse the mobile menu. The result is a consistent, accessible header that adapts across breakpoints and anchors the site’s primary navigation. With the navigation scaffolded, the next step is to implement the Homepage component to establish the landing view’s content and typography.

Homepage Components

The final TypeScript component in this walkthrough is the class HomepageComponent, which defines the content displayed when users first arrive at the application. Unlike the Navbar, which manages navigation and interactivity, this component focuses on presenting introductory text and establishing the site’s central message. Its structure is minimal, containing only a title and a descriptive message, neither of which require communication with the backend to retrieve and both of which will be styled and displayed in the template to form the portfolio’s landing page.

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

@Component({
  selector: 'app-homepage',
  standalone: false,
  templateUrl: './homepage.component.html',
  styleUrl: './homepage.component.scss'
})
export class HomepageComponent {

  title = 'Welcome to My Portfolio';
  message = `
        Thank you for visiting my portfolio. This site serves as a central hub for my academic, professional, and personal work
        in the computer sciences. Here, you will find research projects, technical articles, and interactive applications 
        that showcase some of my interests. My goal with this platform is not only to present my past and ongoing work but 
        also to demonstrate how interdisciplinary research and technology can be applied to address real-world challenges, 
        some of them fun and others more serious as I explore different topics and deepen my understanding of various subjects. 
        I hope this collection provides insight into my approach, sparks new ideas, and opens opportunities for collaboration.
        `;
}

The class HomepageComponent TypeScript file establishes the data structure that will populate the initial view of the portfolio website. It defines a title string for the page heading and a multiline message string that introduces the purpose of the site, highlighting its focus on academic work, professional projects, and interdisciplinary exploration. These properties are exposed to the accompanying template, where they can be styled and rendered, allowing Angular’s binding system to seamlessly connect component logic with the visual presentation of the homepage.

<h3 class="text-center text-shadow-md pt-4"> {{ title }}</h3>
<div class="text-left flex-wrap mx-auto mt-0 mb-0 max-w-3xl max-md:max-w-xl pt-4 text-lg/12 font-serif">
    <div class="pb-4">
    <div class="p-0 text-justify">
        {{ message }}
    </div>
    <p class="my-4">Best,<br><span class="mx-0"> Matthew Hanley</span></p>
    </div>
</div>

This template renders the homepage content by binding the title and message properties from the component class into a styled layout. The heading uses text-center to align the title centrally, text-shadow-md to add subtle depth, and pt-4 for top padding. The surrounding container applies text-left for left-aligned body text, flex-wrap to allow responsive wrapping, and mx-auto with max-w-3xl (and max-md:max-w-xl at smaller breakpoints) to center the content while constraining its width. Typography is refined with pt-4 for spacing, text-lg/12 to set font size and line height, and font-serif for a more formal tone. Inside, the text-justify class evenly distributes the message text across the container for balanced alignment. A final paragraph element (<p>) signs the page with “Best” followed by my name, completing the homepage introduction. This is the last section of this walkthrough. The rest of the website requires communication with the backend, which will be the subject of the next article.

Conclusion

This walkthrough established a complete Angular frontend foundation: initializing a non-standalone project with routing and SCSS, integrating Tailwind and Bootstrap through PostCSS and angular.json, validating the Tailwind build, scaffolding core components, registering them in AppModule, defining routes in AppRoutingModule, and implementing global views alongside a responsive, Tailwind-styled Navbar and a content-focused Homepage. The result is a cohesive structure where navigation, layout, and typography are consistent across breakpoints and ready for feature expansion. The next article walkthrough, "Populating the Courses Components with Backend Data", will guide the integration of the API layer using Angular HttpClient, services, and typed models to fetch, display, and manage course data with clean state handling and template bindings.