Tangy is building your next step
Shaping the lesson around what you want to make.
Tangy is building your next step
Shaping the lesson around what you want to make.
Computer Science
Build a small working to-do list from scratch. You will use familiar HTML structure and CSS selectors while learning the missing pieces: a Node.js project, an Express server, a tiny JSON API, and vanilla JavaScript that updates the page when tasks are added or completed.
Follow the path module by module. The layout keeps the lesson count, your progress, and the module description in one scan.
Make the to-do page stand on its own
2 lessonsCreate the project folder and a polished static interface first, using the learner's existing HTML and CSS foundation. The page will be runnable before any server or API work begins.
Add the Express server and connect the browser
4 lessonsTurn the static page into a running Node.js application, then replace hard-coded task behavior with a tiny server-backed API.
Public lesson
You already know how to organize a document semantically. Now use that skill inside your React app to make the task page feel like a real interface before adding data or backend behavior.
Tasks
Open the component that currently renders the main page of your React app.
Before changing it, find:
Task type, array, or mock dataDo not create a second app entry point. This page should fit into the structure you have already built.
Tasks
Your page needs these visible regions:
Use elements for their meaning:
<main> for the page’s primary content<form> for task creation<label> connected to the text input<ul> and <li> for the list<input type="checkbox"> for completion<button> for actionsDo not use a group of <div> elements where one of these elements describes the content more accurately.
Tasks
Adapt this scaffold to the component and data names in your project. The missing expressions are intentional: you will connect them to your own state or mock data.
<!-- tangeble:starter -->export default function TaskPage() {
// Use the task data your project already has.
// For now, it can be a small local array if no data source exists yet.
const tasks = /* your task collection */;
return (
<main>
<header>
<h1>My tasks</h1>
<p>Keep track of what needs to get done.</p>
</header>
<section aria-labelledby="add-task-heading">
<h2 id="add-task-heading">Add a task</h2>
<form /* connect the submit behavior later */>
<label htmlFor="task-title">Task name</label>
<input
id="task-title"
name="title"
type="text"
placeholder="What needs to be done?"
/* add the value/change connection used by your app */
/>
<button type="submit">Add task</button>
</form>
</section>
<section aria-labelledby="task-list-heading">
<h2 id="task-list-heading">Tasks</h2>
{tasks.length === 0 ? (
<p role="status">You have no tasks yet.</p>
) : (
<ul>
{tasks.map((task) => (
<li key={/* use the task's stable id */}>
<label>
<input
type="checkbox"
checked={/* use the task's completion value */}
onChange={/* connect the completion behavior later */}
/>
<span>{/* render the task title */}</span>
</label>
</li>
))}
</ul>
)}
</section>
</main>
);
}
export default function TaskPage() {
// Use the task data your project already has.
// For now, it can be a small local array if no data source exists yet.
const tasks = /* your task collection */;
return (
<main>
<header>
<h1>My tasks</h1>
<p>Keep track of what needs to get done.</p>
</header>
<section aria-labelledby="add-task-heading">
<h2 id="add-task-heading">Add a task</h2>
<form /* connect the submit behavior later */>
<label htmlFor="task-title">Task name</label>
<input
id="task-title"
name="title"
type="text"
placeholder="What needs to be done?"
/* add the value/change connection used by your app */
/>
<button type="submit">Add task</button>
</form>
</section>
<section aria-labelledby="task-list-heading">
<h2 id="task-list-heading">Tasks</h2>
{tasks.length === 0 ? (
<p role="status">You have no tasks yet.</p>
) : (
<ul>
{tasks.map((task) => (
<li key={/* use the task's stable id */}>
<label>
<input
type="checkbox"
checked={/* use the task's completion value */}
onChange={/* connect the completion behavior later */}
/>
<span>{/* render the task title */}</span>
</label>
</li>
))}
</ul>
)}
</section>
</main>
);
}
For the first pass, it is fine if the form and checkbox do not change data yet. The important part is that the browser can render the complete interface and the controls have the right meaning.
Tasks
Save the file and open the app in the browser.
You should see:
If the page is blank, check the browser console first. A common cause is using a property name that does not match your existing task objects.
Tasks
Temporarily set your task collection to an empty array, using the place where your current mock data lives.
Do not remove the list markup. The conditional should decide which message the user sees.
The browser should now show:
Tasks
You have no tasks yet.
Tasks
You have no tasks yet.
It should not show an empty <ul> with no explanation.
Restore your sample task afterward so you can check both states.
Tasks
Find the property in your task objects that represents completion. It may be called something like completed, done, or isComplete.
Connect that property to the checkbox:
checked should reflect the task’s current valueonChange should call the update path your app already uses, if one existsIf completion behavior does not exist yet, leave the handler as a clearly named placeholder rather than inventing a backend request. The control must still render with the correct checked or unchecked state.
Tasks
function TaskRow({ task, onToggle }) {
return (
<li>
<label>
<input
type="checkbox"
checked={/* task's completion value */}
onChange={() => onToggle(/* task's stable id */)}
/>
<span>{/* task title */}</span>
</label>
</li>
);
}
function TaskRow({ task, onToggle }) {
return (
<li>
<label>
<input
type="checkbox"
checked={/* task's completion value */}
onChange={() => onToggle(/* task's stable id */)}
/>
<span>{/* task title */}</span>
</label>
</li>
);
}
Use this row only if extracting it makes your existing page easier to read. Otherwise, keep the row inside the list. Since your React component experience is still developing, prefer the simpler version that keeps the data flow easy to follow.
Tasks
In the browser, inspect at least two tasks:
If you have connected the update behavior, click a checkbox and confirm its visual state changes. If persistence is not built yet, a refresh may reset it; that is okay for this step.
Tasks
Click the input’s visible label. Focus should move into the text field.
Then type a task title and press Enter.
If your submit logic is not implemented yet, the browser may submit or reload. Preventing submission and adding a task will come in the next behavior step. For now, confirm that:
type="submit"Finally, use the browser’s accessibility tree or inspect the rendered HTML. You should be able to identify the page as a <main>, the task collection as a <ul>, and every task as an <li>.
2 modules · 6 lessons
Make the to-do page stand on its own
Add the Express server and connect the browser
Learn by building your own version.
Remix this public project to open the workspace, follow the guided build, and let the AI mentor teach you through the work instead of doing it for you.