Basic Web Development
with Vue.js
A structured introduction to reactivity, frontend architecture, client-side routing, and API integration — using real Vue 3, HTML, and CSS.
Learning Outcomes
What you'll understand and build by the end of Day 1.
1.1 — Understanding Reactivity in a Web App
Reactivity means the UI automatically updates when data changes — no manual DOM manipulation needed. Vue 3 achieves this through a proxy-based system that watches your data and re-renders only what changed.
The core idea is data binding: connect a variable to a piece of the UI, and they stay in sync at all times.
ref()
Wraps a primitive into a reactive reference. Access via .value in JS.
reactive()
Makes an entire object reactive. Properties tracked deeply.
computed()
Derives a value from reactive data. Recalculates automatically.
watch()
Runs a side effect when a reactive value changes.
Destructuring a reactive() object loses reactivity. Use toRefs() to safely extract individual properties while keeping them reactive.
Computed double: 0 — updates automatically
<script setup>
import { ref, computed } from 'vue'
// ref() creates a reactive number
const count = ref(0)
// computed() derives a value automatically
const doubled = computed(() => count.value * 2)
</script>
<template>
<p>Count: {{ count }}</p>
<p>Double: {{ doubled }}</p>
<button @click="count++">Increment</button>
<button @click="count = 0">Reset</button>
</template>- Create a component using
ref()to track a user's name input in real-time. - Use
computed()to show the character count of the name. - Try destructuring a
reactive()object — see what breaks, then fix it withtoRefs().
1.2 — Developing a Frontend App with HTML, CSS & JavaScript
Vue is a progressive framework — you can drop it into a single HTML file, or build a full Single-File Component (SFC) app. An SFC (.vue file) keeps your template, script, and styles all in one place.
Vue's template syntax extends plain HTML with directives like v-for (loops), v-if (conditionals), and v-model (two-way binding).
Hello, stranger! 👋
<script setup>
// defineProps receives data from a parent component
const props = defineProps({ items: Array })
</script>
<template>
<div class="card-grid">
<!-- v-for loops over an array -->
<article v-for="item in items" :key="item.id">
{{ item.title }}
</article>
</div>
</template>
<!-- scoped: styles only apply to this component -->
<style scoped>
.card-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
</style>1.3 — How Routing Works
In a Single-Page Application (SPA), the browser never fully reloads. Vue Router intercepts navigation and swaps out the visible component — giving users a fast, app-like experience.
You define routes (URL → component mappings), place <RouterView /> where the matched component should render, and use <RouterLink> for navigation instead of plain <a> tags.
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import UserView from '../views/UserView.vue'
import ProfileView from '../views/ProfileView.vue'
const routes = [
{ path: '/', component: HomeView },
{
path: '/user/:id',
component: UserView,
meta: { requiresAuth: true },
children: [
{ path: 'profile', component: ProfileView }
]
}
]
export default createRouter({
history: createWebHistory(),
routes
})// Runs before every route change
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login')
} else {
next()
}
})<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
console.log(route.params.id)
const goHome = () => router.push('/')
</script>
<template>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterView />
</template>1.4 — Making API Requests with the Fetch API
The Fetch API is built into the browser — no library needed. It returns a Promise, so you use async/await to handle responses. Always manage loading and error states so users never see a blank screen.
<script setup>
import { ref } from 'vue'
const data = ref(null)
const loading = ref(false)
const error = ref(null)
// ── GET Request ──────────────────────────────────────
const fetchData = async () => {
loading.value = true
error.value = null
try {
const res = await fetch('https://api.example.com/posts')
if (!res.ok) throw new Error(`HTTP error: ${res.status}`)
data.value = await res.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// ── POST Request ─────────────────────────────────────
const submitForm = async (formData) => {
const res = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
return res.json()
}
</script>
<template>
<div v-if="loading">Loading…</div>
<div v-else-if="error">Error: {{ error }}</div>
<ul v-else>
<li v-for="post in data" :key="post.id">{{ post.title }}</li>
</ul>
</template>HTML — HyperText Markup Language
The skeleton of every web page.
HTML is the standard language for structuring web content. Elements are written as tags, and browsers use them to know what kind of content to display — headings, paragraphs, images, links, and so on.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Page Title</title>
</head>
<body>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</body>
</html>HTML renders top-to-bottom. <!DOCTYPE html> signals HTML5. <head> holds metadata (invisible to users). <body> holds visible content. Headings go from <h1> (most important) to <h6> (least important).
Page Structure
<header>
<h1>Company Name</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Page Title</h1>
<p>Content goes here.</p>
</main>
<footer>
<p>© 2025 Company Name</p>
</footer>Common Tags
🛠️ Try It Yourself
Want to see these tags in action? Follow these simple steps:
- Copy any code snippet from the tabs below
- Open a text editor (Notepad, VS Code, or any editor you prefer)
- Create a new file and paste the code inside the
<body>tags of a basic HTML file - Save the file with a
.htmlextension (e.g.,my-page.html) - Double-click the file to open it in your web browser
💡 Pro tip: Use the Copy button on each code block to grab the code instantly!
<!-- Unordered list (bullet points) -->
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
<!-- Ordered list (numbered) -->
<ol>
<li>Step one</li>
<li>Step two</li>
<li>Step three</li>
</ol><!-- target="_blank" opens in a new tab -->
<a href="https://vuejs.org" target="_blank">Vue.js Docs</a>
<!-- alt text shown if image fails to load -->
<img src="photo.jpg" alt="A descriptive label" loading="lazy"/>
<!-- Responsive image with multiple sizes -->
<picture>
<source srcset="photo-small.jpg" media="(max-width: 600px)"/>
<img src="photo-large.jpg" alt="Photo"/>
</picture><table>
<thead>
<tr><th>Firstname</th><th>Lastname</th><th>Age</th></tr>
</thead>
<tbody>
<tr><td>Jill</td><td>Smith</td><td>50</td></tr>
</tbody>
</table><form action="/submit" method="POST">
<label for="fname">First name:</label>
<input type="text" id="fname" name="fname"/>
<label for="email">Email:</label>
<input type="email" id="email" name="email"/>
<select name="role">
<option value="student">Student</option>
<option value="teacher">Teacher</option>
</select>
<button type="submit">Submit</button>
</form>HTML Attributes
Attributes add extra configuration to elements. They always appear inside the opening tag.
| Attribute | Elements | Description |
|---|---|---|
| id | Global | Unique identifier — used for CSS & JS targeting |
| class | Global | Groups elements for shared CSS styles |
| style | Global | Inline CSS (overrides external stylesheets) |
| hidden | Global | Hides element without removing it from the DOM |
| disabled | button, input, select… | Prevents the user from interacting with the element |
| src | img, audio, video… | URL of the embedded content |
| href | a, link | URL of the linked resource |
| alt | img | Fallback text for screen readers & broken images |
| loading="lazy" | img, iframe | Defers loading until element is near the viewport |
DOM Events
Events are actions that happen in the browser — a click, a keypress, a form submission. You can listen for them and run code in response.
| Event | Triggered when… | Common use |
|---|---|---|
| onclick / @click | User clicks the element | Button actions, toggles |
| onsubmit / @submit | Form is submitted | Validate and send form data |
| onchange / @change | Input value changes (on blur) | Dropdowns, checkboxes |
| oninput / @input | Input changes immediately | Real-time search, char count |
| onkeydown / @keydown | A keyboard key is pressed | Keyboard shortcuts, validation |
| onmouseover / @mouseover | Mouse enters the element | Tooltips, hover highlights |
| onfocus / @focus | Element receives focus | Highlight active input field |
| onblur / @blur | Element loses focus | Validate field on exit |
| onscroll / @scroll | User scrolls the page | Infinite scroll, sticky nav |
<input
type="text"
placeholder="Press any key…"
onkeydown="document.getElementById('out').textContent = 'Key: ' + event.key"
/>
<p id="out"></p>JavaScript — The Language of the Web
Add interactivity and dynamic behavior to your web pages.
What is JavaScript?
JavaScript is a programming language created by Brendan Eich in 1995. It was famously designed in about 10 days, which explains some of its quirks and design trade-offs.
The language was inspired by the Scheme programming language and was intended to be approachable, flexible, and easy to embed into web pages. One of its main highlights is its familiar syntax, which resembles Java and other C-like languages, making it easier for beginners to pick up.
JavaScript supports multiple programming paradigms:
- Object-Oriented Programming (OOP)
- Procedural Programming
- Functional Programming (FP)
Because it supports functional programming, functions are first-class citizens. This means functions can be:
- Stored in variables
- Passed as arguments
- Returned from other functions
In JavaScript, functions are treated just like any other value.
Where is JavaScript used?
JavaScript is most commonly used in web browsers. It was designed for scripting tasks such as manipulating elements in an HTML document, responding to user actions, and updating content dynamically without reloading the page.
A Simple Example
<html>
<body>
<p id="greeting">Hello</p>
<button onclick="changeText()">Click me</button>
<script>
function changeText() {
document.getElementById("greeting").innerText = "Hello World";
}
</script>
</body>
</html>- The button triggers changeText() — The
onclickattribute tells the browser: "When this button is clicked, call the function namedchangeText." - document represents the page —
documentis a global object provided by the browser. It represents the entire HTML page loaded in the tab and acts as the entry point to the DOM (Document Object Model) — the browser's in-memory tree of all elements on the page. - getElementById("greeting") finds the element — This method searches the DOM and returns the element whose id matches "greeting". In this case, it returns the
<p>element. - .innerText = "Hello World" updates the page — Once you have a reference to the element, you can modify its properties. Changing
innerTextimmediately updates the visible text on the page — no reload required.
Other things you can do with elements
element.innerHTML = "<span>Hello</span> World"— insert HTMLelement.style.color = "red"— modify CSS styleselement.appendChild(newNode)— add child elements dynamically
getElementById is designed to return one element. If multiple elements share the same id, behavior becomes unpredictable. More importantly, the HTML specification states that duplicate IDs are invalid. CSS selectors, accessibility tools (like screen readers), and automated tests all rely on IDs being unique. Duplicate IDs can lead to subtle bugs that are very difficult to debug.
JavaScript Basics: Functions
Functions are one of the core building blocks of JavaScript.
A function is a reusable block of code that runs when it is called. There are two main steps:
- Declare the function
- Call the function
function changeText(id) {
// function body
return "something";
}A function declaration consists of:
- The function body
- Parameters
- A return value
1. Function body
The code inside { } runs every time the function is called.
2. Parameters (arguments)
Parameters are inputs passed into a function.
function greet(name) {
return "Hello, " + name;
}
// Calling greet("Aiman") assigns "Aiman" to name
// Multiple parameters:
function add(a, b) {
return a + b;
}If you pass fewer arguments than expected, the missing ones become undefined. JavaScript allows this silently, which can sometimes cause bugs if you're not careful.
3. Return value
The return statement sends a value back to the caller and immediately exits the function.
function add(a, b) {
return a + b;
console.log("this never runs"); // unreachable code
}
let result = add(3, 4); // 7
// If there is no return, the function returns undefinedCalling a function
function greet(name) {
return "Hello, " + name;
}
greet("Aiman"); // return value ignored
let msg = greet("Aiman"); // return value stored
console.log(msg); // "Hello, Aiman"
// This is exactly what happens with onclick="changeText()" in HTML
// — the browser calls the function automatically when the event occurs.Variables
Variables store data such as numbers, strings, arrays, and objects.
let a = "Hello World";
console.log(a); // Hello World
// What's happening:
// let a = "Hello World" declares a variable and assigns a value to it.
// console.log(a) prints the value to the developer console.In mathematics: x = 5 means "x is always 5". In programming: x = 5; x = 10; means "store 5 in x, then later replace it with 10".
let x = 5;
x = 10;
console.log(x); // 10let vs const
JavaScript provides two main keywords for variables:
let— value can changeconst— value cannot be reassigned
let score = 0;
score = 10; // allowed
const MAX_SCORE = 100;
MAX_SCORE = 200; // error: Assignment to constant variable- Create a function that takes a name and returns a personalized greeting.
- Use
getElementByIdto change the text of an element when a button is clicked. - Experiment with
letvsconst— try reassigning both and observe what happens.
CSS — Cascading Style Sheets
Control how your HTML looks and feels.
CSS describes the presentation of HTML. You can attach styles via a linked .css file, a <style> block inside HTML, or directly via a style attribute on an element.
/* Link in HTML: <link rel="stylesheet" href="style.css"> */
body {
background-color: lightblue;
font-family: sans-serif;
}
h1 {
color: white;
text-align: center;
}
/* Select by class */
.card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
/* Select by id */
#main-title {
font-size: 2rem;
font-weight: bold;
}CSS Selectors
/* All <p> elements */
p { color: red; }
/* Direct children only */
.parent > .child { color: blue; }
/* Hover state */
button:hover { background: green; }
/* First and last child */
li:first-child { font-weight: bold; }
li:last-child { color: gray; }
/* Pseudo-elements — generated content */
.card::before {
content: '★ ';
color: gold;
}
/* CSS Custom Properties — define once, use everywhere */
:root {
--primary: #2d6a4f;
--font-size: 16px;
}
h1 { color: var(--primary); }
p { font-size: var(--font-size); }Tailwind CSS
Tailwind is a utility-first CSS framework. Instead of writing custom CSS rules, you apply small pre-built classes directly in your HTML markup. This keeps styles co-located with your structure and speeds up development significantly.
<!-- Traditional CSS approach -->
<div class="card">Hello</div>
<!-- Then write: .card { padding: 1rem; background: white; } -->
<!-- Tailwind approach: classes do the work directly -->
<div class="p-4 bg-white rounded shadow hover:shadow-lg transition">
Hello
</div>
<!-- Responsive: md: applies on medium screens and up -->
<div class="text-sm md:text-base lg:text-lg">
Responsive text
</div>
<!-- Dark mode support -->
<div class="bg-white dark:bg-gray-900 text-black dark:text-white">
Adapts to system theme
</div>Full Tailwind documentation at tailwindcss.com. Use the search to find any property — type "flex", "grid", "padding" and you'll immediately see the right utility class.
Facilitation Guide
Tips for instructors and session leads.
🖐️ Hands-On First
Start each module with a 5-minute coding challenge before theory.
🌍 Real-World Context
Connect concepts to production examples.
👥 Peer Code Review
Swap screens for 3-minute feedback after exercises.
❓ Q&A Breaks
Allocate time after each outcome for questions.
⚠️ Anticipate Pitfalls
Watch for reactivity loss, router mismatches, CORS errors.
💬 Feedback Loop
End sessions with "What will you build tomorrow?"
Quick Start Commands
# Create a new Vite project (official scaffolding)
$ npm create vite@latest my-app
# Select: Vue + JavaScript
# Install dependencies and start dev server
$ cd my-app && npm install && npm run dev
# → http://localhost:5173
# Install tailwindcss and @tailwindcss/vite via npm.
$ npm install tailwindcss @tailwindcss/vite
Configure the Vite plugin
#Add the @tailwindcss/vite plugin to your Vite configuration.
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
Import Tailwind CSS
#Add an @import to your CSS file that imports Tailwind CSS.
@import "tailwindcss";
Vue 3 Docs: vuejs.org/guide · Vue Router: router.vuejs.org · Tailwind CSS: tailwindcss.com · MDN Web Docs: developer.mozilla.org