Main Axis vs. Cross Axis
flex-direction: row: Main axis is horizontal
flex-direction: column: Main axis is vertical
React Ecosystem and Modern Frameworks
Chen Ying
Assistant Professor, Teaching Stream
Department of Electrical and Computer Engineering
University of Toronto
React Router: <RouterProvider />, createBrowserRouter()
<Link />, useNavigate()useParams()Data Fetching: fetch() + useEffect()
Form Handling
<form onSubmit={handleSubmit}>
preventDefault(): Prevent page reloadMulti-Select Dropdown: <select multiple>, <option>
Basic Styling
Cascading Style Sheets: Describes how HTML elements look
| Selector | Example | Example Description |
|---|---|---|
| element | h1 |
Selects all <h1> elements |
| .className | .title |
Selects all elements with class="title" |
| #idName | #firstname |
Selects the element with id="firstname" |
* |
* |
Selects all elements |
Why is it called Cascading Style Sheets?
Because styles can:
Every HTML element is a rectangle
<h1> is actually a rectangle on the screenBrowsers render everything as boxes
Each element consists of content (text/image), padding, border, margin
Space appears between content and border
Space appears outside the border
Padding: Space appears between content and border
Margin: Space appears outside the border
App.tsx
How do we place boxes horizontally?
How do we center them?
How do we control spacing?
Most common layout system in modern apps
Flexbox always has two axes:
flex-direction: row: Main axis is horizontal
flex-direction: column: Main axis is vertical
Stack items vertically
justify-content controls spacing along main axis
Write custom stylesheets
.css filesTraditional CSS works well
But in large applications:
This is a scaling challenge, not a flaw in CSS
Instead of inventing class names, use small single-purpose utility classes
Examples
flexjustify-centeritems-centerbg-blue-50This is the philosophy behind Tailwind CSS
A utility-first CSS framework
Instead of writing
Tailwind does NOT remove CSS
It generates CSS classes like:
You are still using CSS, just not writing custom class names
Set up and use Tailwind CSS in Vite-based React projects
Doc: tailwindcss.com/docs/installation/using-vite
Install Tailwind CSS
Add @tailwindcss/vite plugin to Vite configuration
Add @import "tailwindcss"; to CSS files that import Tailwind CSS
Combine single-purpose utility classes
App.tsx
function App() {
return (
<div className="flex justify-center items-center p-4 bg-blue-50 rounded-4xl">
<h1 className="text-8xl font-bold text-blue-600 bg-yellow-50 border-8 border-black p-4 m-8">
Box 1
</h1>
<h1 className="text-8xl font-bold text-blue-600 bg-yellow-50 border-8 border-black p-4 m-8">
Box 2
</h1>
</div>
);
}
export default App;flex, justify-center, items-centerp-4, m-8text-8xl, font-boldbg-blue-50, text-blue-600, bg-yellow-50border-8, border-blackWe compose styles instead of inventing class names
Utility Classes (p-4, text-8xl, bg-blue-50)
Responsive Design
Adjust layouts, text, and spacing based on screen size
Every utility class in Tailwind can be conditionally applied at different breakpoints (specific screen widths)
Tailwind supports five default breakpoints
| Prefix | Min Width | Example Device |
|---|---|---|
sm: |
40rem (640px) | Small tablets |
md: |
48rem (768px) | Tablets |
lg: |
64rem (1024px) | Laptops |
xl: |
80rem (1280px) | Large screens |
2xl: |
96rem (1536px) | Extra large screens |
text-4xl: Default text size on all screensmd:text-6xl: Changes to text-6xl on screens 768px and largerlg:text-8xl: Changes to text-8xl on screens 1024px and largerApp.tsx
function App() {
return (
<div className="flex justify-center items-center p-4 bg-blue-50 rounded-4xl">
<h1 className="text-4xl md:text-6xl lg:text-8xl font-bold text-blue-600 bg-yellow-50 border-8 border-black p-4 m-8">
Box 1
</h1>
<h1 className="text-4xl md:text-6xl lg:text-8xl font-bold text-blue-600 bg-yellow-50 border-8 border-black p-4 m-8">
Box 2
</h1>
</div>
);
}
export default App;className StringsUtility classes can become long
App.tsx
function App() {
const titleClass =
"text-4xl md:text-6xl lg:text-8xl font-bold text-blue-600 bg-yellow-50 border-8 border-black p-4 m-8";
return (
<div className="flex justify-center items-center p-4 bg-blue-50 rounded-4xl">
<h1 className={titleClass}>Box 1</h1>
<h1 className={titleClass}>Box 2</h1>
</div>
);
}
export default App;Utility Classes (p-4, text-8xl, bg-blue-50)
Responsive Design (sm:p-8, lg:text-8xl)
Dynamic Styling
Utility classes work naturally with props
components/Title.tsx
type TitleProps = {
variant?: "primary" | "secondary";
children: React.ReactNode;
};
function Title({ variant = "primary", children }: TitleProps) {
const variantStyles = {
primary: "bg-yellow-50 border-yellow-500",
secondary: "bg-green-50 border-green-800",
};
return (
<h1
className={`text-4xl md:text-6xl lg:text-8xl font-bold text-blue-600 border-8 p-4 m-8 ${
variantStyles[variant]
}`}
>
{children}
</h1>
);
}
export default Title;Use in App.jsx
Utility Classes (p-4, text-8xl, bg-blue-50)
Responsive Design (sm:p-8, lg:text-8xl)
Dynamic Styling
Theme Customization
Instead of hardcoding colors everywhere
Define design tokens once
Theme variables are defined in namespaces
Each namespace corresponds to one or more utility class
--color-*: Color utilities--text-*: Font size utilities--spacing-*: Spacing and sizing utilitiesA utility-first CSS framework with pre-built single-purpose classes for rapid UI development
p-4, text-8xl, bg-blue-50)sm:p-8, lg:text-8xl)@theme)Tailwind enables composing styles with utility classes
But real apps repeat the same UI patterns
className stringsSo instead of repeating utilities everywhere, we extract them into reusable components
components/Title.tsx
type TitleProps = {
variant?: "primary" | "secondary";
children: React.ReactNode;
};
function Title({ variant = "primary", children }: TitleProps) {
const variantStyles = {
primary: "bg-yellow-50 border-yellow-500",
secondary: "bg-green-50 border-green-800",
};
return (
<h1
className={`text-4xl md:text-6xl lg:text-8xl font-bold text-blue-600 border-8 p-4 m-8 ${
variantStyles[variant]
}`}
>
{children}
</h1>
);
}
export default Title;We kept Tailwind’s benefits:
md:, lg:)And we gained:
Modern frontend development combines:
Utility-First CSS: Small, single-purpose classes
Component-Driven Development: Build small reusable UI units
Our Title component is a small example of both
A collection of accessible, Tailwind-styled components
Benefits:
Tailwind: Utilities to build anything
shadcn/ui: Well-designed, reusable components built from those utilities
Think of it as: Utilities → Extract patterns → Standardized components
ui.shadcn.com/docs/installation
tsconfig.jsontsconfig.app.jsontsconfig.app.json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}vite.config.tsvite.config.ts
Initialize shadcn/ui
components.jsonsrc/global.csssrc/lib/utils.tsAdd a component
src/components/ui/button.tsx
ButtonVariants are just predefined Tailwind class combinations
src/App.jsx
import { Button } from "@/components/ui/button";
function App() {
return (
<div className="flex flex-col justify-center items-center p-4 space-y-4 bg-blue-50 rounded-4xl">
<Button variant="default">Default Button</Button>
<Button variant="destructive">Destructive Button</Button>
<Button variant="ghost">Ghost Button</Button>
</div>
);
}
export default App;ButtonCustomize the component’s appearance with Tailwind CSS
src/App.jsx
import { Button } from "@/components/ui/button";
function App() {
return (
<div className="flex flex-col justify-center items-center p-4 space-y-4 bg-blue-50 rounded-4xl">
<Button variant="default">Default Button</Button>
<Button className="bg-blue-500 hover:bg-blue-600">Blue Button</Button>
</div>
);
}
export default App;Or modify src/components/ui/button.tsx directly
Tailwind provides the styling foundation
shadcn/ui provides reusable, accessible components built on top
Together they form a modern frontend styling architecture
Tailwind CSS: A utility-first CSS framework with predefined classes for rapid UI development
shadcn/ui: A set of customizable, copy-and-paste components styled with Tailwind CSS
You will use both in Assignment 4
Full-Stack Next.js application with model styling
The schedule has been adjusted to ensure that all required Next.js concepts are covered in lecture before the handout is release
A full-stack React framework
Next.js adds important capabilities on top of React
--yes skips prompts using saved preferences or defaultsThe default setup enables
@/*app/page.tsxDefines homepage (/)
This looks like React, but it runs on the server by default
In a typical React Single Page Application
Browser requests /
Server returns a mostly empty HTML file:
Browser downloads a large JavaScript bundle
React runs in the browser
React generates the UI
All future navigation happens client-side
Rendering happens in the browser
This is called Client-Side Rendering (CSR)
useEffect/This is called Server-Side Rendering (SSR)
By default, Next.js renders all pages on the server
By default, components are Server Components
Render on the server
Can use async/await directly
Can fetch data without useEffect
Cannot use React hooks like useState or useEffect
Cannot access browser-only APIs (e.g., window)
A component must be a Client Components if it needs
useState, useEffect)onClick)window, document)at the top of the file
components/Counter.tsx
This component runs in the browser
Server-Side Rendering for speed
Home stays server-side, rendering fastClient-Side Rendering for action
Counter loads on the client for interactivityui.shadcn.com/docs/installation/next
components.jsonapp/global.csslib/utils.tsapp/page.tsx
Next.js uses files to define routes
pages/ directory, where each .tsx or .js file inside represents a route
pages/about.js → /aboutapp/ directory, where each folder inside represents a route
app/about/page.tsx → /aboutEach folder in the app/ directory represents a route
Each folder contains a page.tsx, which defines a page (route endpoint)
When users visit a route, Next.js renders the corresponding page.tsx file
/: app/page.tsx/about: app/about/page.tsxapp/about/page.tsx
Static Route: Fixed path
Static Routes: Fixed paths
app/about/page.tsx creates the /about routeDynamic Routes: Flexible paths, changed based on URL
src/main.tsx
Use square brackets ([]) to define dynamic segments
Example: app/posts/[id]/page.tsx creates /posts/1, /posts/2, etc.
app/posts/[id]/page.tsx
params.id: Contains the dynamic segment from the URL
/posts/1 → params.id = "1"Error: Route "/posts/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
at PostPage (app/posts/[id]/page.tsx:6:34)
4 |
5 | function PostPage({ params }: PostParams) {
> 6 | const postId = parseInt(params.id, 10);
| ^
7 | return <h1>Post #{postId}</h1>;
8 | }
9 |
Earlier assumptions: params was always synchronously available
In App Router, dynamic route parameters in Server Components are treated as potentially asynchronous
useEffect requiredparams is a Promise in dynamic routes
app/posts/[id]/page.tsx
type PostParams = {
params: Promise<{ id: string }>; // params is a Promise
};
export default async function PostPage({ params }: PostParams) {
const resolvedParams = await params; // Await params to resolve the Promise
const postId = parseInt(resolvedParams.id, 10);
return (
<main className="flex items-center justify-center">
<h1 className="text-6xl font-bold">Post #{postId}</h1>
</main>
);
}app/posts/[id]/page.tsx
type PostParams = {
params: Promise<{ id: string }>; // params is a Promise
};
export default async function PostPage({ params }: PostParams) {
const { id } = await params; // Destructure after awaiting
const postId = parseInt(id, 10);
return (
<main className="flex items-center justify-center">
<h1 className="text-6xl font-bold">Post #{postId}</h1>
</main>
);
}In dynamic route contexts, params is a Promise
Always await params in Server Components
notFound()Next.js automatically renders built-in 404 page
app/posts/[id]/page.tsx
import { notFound } from "next/navigation";
type PostParams = {
params: Promise<{ id: string }>;
};
export default async function PostPage({ params }: PostParams) {
const { id } = await params;
const postId = Number.parseInt(id, 10);
if (Number.isNaN(postId)) {
return notFound();
}
return (
<main className="flex items-center justify-center">
<h1 className="text-6xl font-bold">Post #{postId}</h1>
</main>
);
}app/about/page.tsx creates the /about routeapp/posts/[id]/page.tsx creates /posts/1, /posts/2, …CSS Foundations
Tailwind CSS: A utility-first CSS framework
p-4, text-8xl, bg-blue-50)sm:p-8, lg:text-8xl)@theme)Modern Styling Solutions
Tailwind CSS: A utility-first CSS framework with predefined classes for rapid UI development
shadcn/ui: A modern component collection
Next.js A full-stack React framework
async/await"use client";): Run in browser
/about)/posts/[id])