Notepad / Code Sandbox is part of Practice Tools. Sign in or create an account, then subscribe for โฆ4,000/month to unlock it.
Step-by-step exercises for Notepad / Code Sandbox - read them here, then practice live in the tool above.
This gives you three writing areas โ HTML, CSS, and JavaScript โ plus a live preview panel where your page appears as you type.
HTML is the structure of a page. Type exactly this:
<h1>Hello, I'm coding!</h1>
<p>This is my first webpage.</p>
Your plain text becomes a real webpage. This "write โ run โ look" loop is the single most important habit to build.
CSS controls appearance. Type:
h1 {
color: darkgreen;
font-family: Arial;
}
body {
background: #f4f4f4;
}
Edit darkgreen to darkred (or any CSS color name) and click Run again.
Below your paragraph in the HTML box, add:
<button id="myBtn">Click me</button>
In the JS box, type:
document.getElementById("myBtn").onclick = function() {
document.querySelector("h1").textContent = "You clicked it!";
};
id matches exactly (myBtn) in both boxes.Delete the closing } from your JavaScript and click Run.
} back and confirm it works again. This is what debugging feels like โ it's normal, even for professionals.Click Save to keep this under My Projects, so you can reopen or share it later.
Say it out loud or write it down: "I built a page with a heading, styled text, and a button that changes the heading when clicked." If you can say that without looking at your code, you actually understood it.
In your HTML box, add a large number display and two buttons โ one to increase, one to decrease:
<h1 id="count">0</h1>
<button id="increase">+</button>
<button id="decrease">โ</button>
In JavaScript, store the real number in a variable and update the display from that variable โ never change the text directly without updating the variable too.
let count = 0;
document.getElementById("increase").onclick = function() {
count = count + 1;
document.getElementById("count").textContent = count;
};
count) is the "source of truth." The number on screen is just a reflection of it โ that idea is the core of this whole lab.Add the matching handler, subtracting instead of adding:
document.getElementById("decrease").onclick = function() {
count = count - 1;
document.getElementById("count").textContent = count;
};
In CSS, make the number large, bold, and centered โ a user should read it at a glance:
#count {
font-size: 48px;
text-align: center;
}
button {
padding: 8px 16px;
font-size: 18px;
}
This is the real intermediate skill โ handling what happens at the boundary, not just the normal path. Guard the decrease so it can't go below zero:
document.getElementById("decrease").onclick = function() {
if (count > 0) {
count = count - 1;
document.getElementById("count").textContent = count;
}
};
In HTML add a third button, and in JavaScript wire it up to restore the starting value:
<button id="reset">Reset</button>
document.getElementById("reset").onclick = function() {
count = 0;
document.getElementById("count").textContent = count;
};
Click increase and decrease rapidly and out of order. Click reset mid-run. Click decrease many times right after loading the page. Ask yourself: does the number on screen ever disagree with what the real count should be?
Every single time you changed count, you also had to remember to update the text on screen in the same place. In a small project that's manageable by hand โ forgetting that second line is one of the most common real-world bugs, and it's exactly the problem that more advanced frameworks are built to solve later on.
Once it behaves correctly at zero, at large numbers, and after reset, click Save to keep it under My Projects.
We'll build a to-do list: add an item, mark it done, delete it, and show a live count remaining. Don't build the whole thing yet โ start with just the structure:
<input id="taskInput" placeholder="New task">
<button id="addBtn">Add</button>
<ul id="taskList"></ul>
<p id="remaining">0 remaining</p>
Store tasks in an array โ your single source of truth, exactly like the count variable in Lab 2, just holding a list instead of a number:
let tasks = [];
document.getElementById("addBtn").onclick = function() {
let text = document.getElementById("taskInput").value;
if (text.trim() === "") return;
tasks.push({ text: text, done: false });
document.getElementById("taskInput").value = "";
render();
};
function render() {
let list = document.getElementById("taskList");
list.innerHTML = "";
tasks.forEach(function(task, index) {
let li = document.createElement("li");
li.textContent = task.text;
list.appendChild(li);
});
}
Inside the forEach, add a checkbox that updates the array (not just the screen) when clicked:
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = task.done;
checkbox.onclick = function() {
tasks[index].done = !tasks[index].done;
render();
};
li.prepend(checkbox);
if (task.done) {
li.style.textDecoration = "line-through";
}
Add a delete button per item that removes it from the array itself:
let deleteBtn = document.createElement("button");
deleteBtn.textContent = "โ";
deleteBtn.onclick = function() {
tasks.splice(index, 1);
render();
};
li.appendChild(deleteBtn);
This is what separates a pile of features from a real app โ parts that stay in sync. Update it inside render(), since that's your one place the display refreshes:
let remainingCount = tasks.filter(function(t) { return !t.done; }).length;
document.getElementById("remaining").textContent = remainingCount + " remaining";
Space out list items, dim completed tasks, and style the input/buttons so the app feels intentional rather than like a bare test page.
Click Add with an empty input. Add the same task twice. Delete every task and check what "remaining" shows at zero. Check and uncheck rapidly. Fix anything that misbehaves.
Once every feature works correctly on its own and together, click Save to keep it under My Projects.