Week 6

useEffect - Side Effects, Data Fetching & Async State

Browser DevTools - The Network Tab

Tailwind v4

React Router

Deployment with Vercel

Practice

Assignment

Front end Track

Styling with Tailwind CSS v4

You already know how to write CSS. You understand the box model, Flexbox, Grid, custom properties, and media queries. This chapter doesn't replace any of that — it introduces a different way of applying styles that you'll encounter constantly in professional React projects: Tailwind CSS.

What Is Tailwind CSS?

https://www.youtube.com/watch?v=mr15Xzb1Ook

Tailwind is a utility-first CSS framework. Instead of writing CSS rules in a separate stylesheet, you apply small, single-purpose classes directly in your JSX. Each class does exactly one thing: font-bold sets font-weight: bold, mt-4 sets margin-top: 1rem, flex sets display: flex.

The result looks like this:

// Native CSS approach — two files to manage
<button className="submit-button">Send</button>

// Tailwind approach — everything inline
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
  Send
</button>

At first glance, the Tailwind version looks busier. That reaction is normal and expected, but it passes quickly once the mental model clicks.

How It Differs From Native CSS

The meaningful differences aren't about syntax, they're about workflow and constraints.

No switching files. With native CSS you write markup in one file and styles in another. With Tailwind, styles live where the element lives. When you delete a component, its styles disappear with it. No unused CSS to remove!

A design system by default. Tailwind's utility classes aren't arbitrary values — they come from a consistent scale. Spacing uses an 8-point grid (p-1 = 4px, p-2 = 8px, p-4 = 16px, p-8 = 32px). Colours come in numbered shades from 50 to 950. Font sizes, borders, shadows — all consistent. Writing native CSS, you make these decisions yourself every time. With Tailwind, the decisions are already made and applied consistently across your whole project.

No naming things. One of the genuinely hard parts of CSS is naming classes: .card-header, .article-meta, .nav-item--active. With Tailwind, you don't name anything. This is a bigger time-saver than it sounds.

What you give up. Tailwind components can have long className strings. Some developers find this harder to scan than a named CSS class. And because styles are colocated with markup, extracting a design pattern to reuse it requires creating a React component. Which is usually the right move anyway, but it's a different refactoring habit than extracting a CSS class.

<aside> 💡

Info

Tailwind doesn't replace CSS knowledge, instead it uses CSS under the hood. Understanding what flex, grid, position, and overflow actually do is what lets you reach for the right Tailwind classes confidently. Everything you learned in Week 2 applies here.

</aside>

Setting Up Tailwind v4 in a Vite Project

If you created your project with Vite, Tailwind isn't included by default. Install it with:

npm install tailwindcss @tailwindcss/vite

Then add the Tailwind plugin to your vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
});

Then add a single line to the top of your src/index.css:

@import "tailwindcss";

Make sure index.css is imported in your main.tsx:

import './index.css';

That's the entire setup. No configuration file required — Tailwind v4 detects your source files automatically. Start the dev server and you're ready to use utility classes immediately.

<aside> ⚠️

Warning

If you find tutorials showing @tailwind base, @tailwind components, and @tailwind utilities directives in the CSS file: those are Tailwind v3 instructions. In v4, all three are replaced by the single @import "tailwindcss" line. The old directives don't work in v4.

</aside>

Core Utility Classes

Tailwind has classes for virtually every CSS property. You don't need to memorise them — the Tailwind docs are fast to search, and the VS Code extension (Tailwind CSS IntelliSense) autocompletes as you type. What follows are the classes you'll reach for most often.

Layout

// display
<div className="flex">...</div>          // display: flex
<div className="grid">...</div>          // display: grid
<div className="hidden">...</div>        // display: none
<div className="block">...</div>         // display: block

// flexbox
<div className="flex items-center justify-between gap-4">
  {/* align-items: center, justify-content: space-between, gap: 1rem */}
</div>

// grid
<div className="grid grid-cols-3 gap-6">
  {/* 3-column grid with 1.5rem gap */}
</div>

Spacing

Tailwind's spacing scale: 1 = 4px, 2 = 8px, 3 = 12px, 4 = 16px, 6 = 24px, 8 = 32px, 12 = 48px, 16 = 64px.

// padding
<div className="p-4">...</div>           // padding: 1rem (all sides)
<div className="px-6 py-3">...</div>     // padding: 0.75rem 1.5rem (x = horizontal, y = vertical)
<div className="pt-2 pb-4">...</div>     // padding-top / padding-bottom

// margin
<div className="m-auto">...</div>        // margin: auto
<div className="mt-8 mb-4">...</div>     // margin-top / margin-bottom
<div className="mx-4">...</div>          // margin-left and margin-right

Sizing

<div className="w-full">...</div>        // width: 100%
<div className="w-1/2">...</div>         // width: 50%
<div className="max-w-2xl">...</div>     // max-width: 42rem
<div className="h-screen">...</div>      // height: 100vh
<div className="min-h-screen">...</div>  // min-height: 100vh

Typography

<h1 className="text-4xl font-bold tracking-tight">Heading</h1>
{/* font-size: 2.25rem, font-weight: 700, letter-spacing: -0.025em */}

<p className="text-base text-gray-600 leading-relaxed">Body text</p>
{/* font-size: 1rem, color: #4b5563, line-height: 1.625 */}

<span className="text-sm font-medium uppercase">Label</span>

Colours

Tailwind includes a full colour palette. Each colour has shades from 50 (very light) to 950 (very dark):

<div className="bg-blue-600 text-white">...</div>
<div className="bg-gray-100 text-gray-900">...</div>
<div className="border border-gray-200">...</div>
<p className="text-red-500">Error message</p>

Borders and Rounded Corners

<div className="border border-gray-200 rounded-lg">...</div>
// border: 1px solid #e5e7eb; border-radius: 0.5rem

<button className="rounded-full px-6 py-2">Pill button</button>
<div className="rounded-none">Sharp corners</div>

Shadows

<div className="shadow-sm">...</div>     // subtle shadow
<div className="shadow-md">...</div>     // medium shadow
<div className="shadow-lg">...</div>     // large shadow
<div className="shadow-none">...</div>   // no shadow

<aside> ⌨️

Hands On

Open your Next.js project and replace the contents of src/app/page.tsx with this:

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-50 flex items-center justify-center p-8">
      <div className="bg-white rounded-2xl shadow-md p-8 max-w-md w-full">
        <h1 className="text-3xl font-bold text-gray-900 mb-2">Hello Tailwind</h1>
        <p className="text-gray-500 mb-6">Styling without leaving your JSX.</p>
        <button className="bg-blue-600 text-white px-4 py-2 rounded-lg w-full hover:bg-blue-700">
          Get started
        </button>
      </div>
    </main>
  );
}

Read each class and try to predict what it does before checking the docs. Then experiment: change blue-600 to emerald-600, change rounded-2xl to rounded-none, swap shadow-md for shadow-xl.

</aside>

States: Hover, Focus, and Active

Tailwind handles interactive states with prefixes applied directly to the relevant class:

<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
  Hover me
</button>

<input className="border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 rounded px-3 py-2 outline-none" />

<a className="text-blue-600 hover:text-blue-800 hover:underline">Link</a>

The focus:ring pattern is especially important: it's Tailwind's way of providing a visible focus indicator, which you need for keyboard accessibility. The equivalent of the :focus-visible work you did in Week 2.

{/* Accessible focus ring pattern */}
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2">
  Accessible button
</button>

Responsive Design

In Week 2 you wrote media queries by hand. Tailwind handles responsive design with breakpoint prefixes: sm:, md:, lg:, xl:.

Tailwind is mobile-first — classes without a prefix apply to all screen sizes, and prefixed classes apply from that breakpoint upward.

Prefix Breakpoint
(none) All screens
sm: 640px and up
md: 768px and up
lg: 1024px and up
xl: 1280px and up
// Single column on mobile, 3 columns on desktop
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
  ...
</div>

// Text size that grows with screen size
<h1 className="text-2xl md:text-4xl lg:text-5xl font-bold">
  Responsive heading
</h1>

// Hidden on mobile, visible from md up
<aside className="hidden md:block">
  Sidebar
</aside>

// Full width on mobile, auto width from sm up
<button className="w-full sm:w-auto px-6 py-2 bg-blue-600 text-white rounded-lg">
  Submit
</button>

This maps directly to what you already know. md:grid-cols-3 is equivalent to:

@media (min-width: 768px) {
  .my-element {
    grid-template-columns: repeat(3, 1fr);
  }
}

The difference is you don't write the media query, you just prefix the class.

<aside> ⌨️

Hands On

Build a simple card grid that shows one column on mobile and two columns from md: upward. Add a heading that's text-xl on mobile and text-3xl on lg:. Open the browser's device toolbar (Cmd/Ctrl + Shift + M) and resize to verify the breakpoints are working.

</aside>

Customising with @theme

Tailwind v4 replaces the old tailwind.config.js file with CSS-first configuration using @theme directly in your globals.css. This is where you define your project's design tokens — custom colours, fonts, or spacing values that extend Tailwind's defaults.

/* src/app/globals.css */
@import "tailwindcss";

@theme {
  /* Custom brand colours — now available as bg-brand-500, text-brand-600, etc. */
  --color-brand-50: #eff6ff;
  --color-brand-500: #2563eb;
  --color-brand-600: #1d4ed8;
  --color-brand-900: #1e3a8a;

  /* Custom fonts — available as font-display */
  --font-display: "Cal Sans", sans-serif;

  /* Custom breakpoint */
  --breakpoint-xs: 480px;
}

Once defined, these tokens work exactly like built-in Tailwind utilities:

<h1 className="font-display text-brand-600">Portfolio</h1>
<div className="bg-brand-50 border border-brand-500">...</div>

All design tokens defined in @theme are also automatically available as CSS custom properties at runtime. So --color-brand-500 is accessible in any CSS you write as var(--color-brand-500) — useful when you need to use a theme value in a context where a Tailwind class won't reach.

For this week, you likely won't need custom theme tokens. Tailwind's built-in palette and scale are more than enough for converting your portfolio. But knowing where customisation lives means you're not stuck when you need it.

Arbitrary Values

Sometimes you need a value that isn't in Tailwind's scale: a specific pixel measurement from a design, an unusual colour, a one-off size. Tailwind supports arbitrary values with square bracket notation:

<div className="w-[340px] h-[200px]">...</div>
<p className="text-[#e63946]">Custom colour</p>
<div className="mt-[3px]">Pixel-perfect nudge</div>
<div className="grid grid-cols-[1fr_2fr_1fr]">...</div>

<aside> ⚠️

</aside>

Keeping className Strings Readable

Long className strings are the main readability concern with Tailwind. A few conventions help.

Group by concern. Put layout classes first, then spacing, then typography, then colour, then interactive states:

// Harder to scan — no clear order
<button className="hover:bg-blue-700 text-white text-sm bg-blue-600 px-4 font-medium rounded-lg py-2">

// Easier — layout → spacing → typography → colour → states
<button className="rounded-lg px-4 py-2 text-sm font-medium bg-blue-600 text-white hover:bg-blue-700">

Extract to a component when a pattern repeats. If you're writing the same ten classes on every button in your project, make a Button component:

Next.js - Structure & Routing