The Document Object Model, or DOM, represents a loaded document as objects. JavaScript can find elements, read their properties and update their content. The browser provides this API; it is not available in an ordinary Node program. HTML supplies structure and semantics, CSS handles presentation, and JavaScript adds behavior.
A complete browser exercise
Save index.html and app.mjs together. Serve the folder with a local HTTP server, for example python3 -m http.server 8000 --bind 127.0.0.1, then open http://127.0.0.1:8000. Use a separate folder containing only the exercise, and stop the server when finished. Module loading through file:// has restrictions and is not a reliable replacement for HTTP.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Study goal</title>
<body>
<h1>Today's study goal</h1>
<form id="goal-form">
<label for="topic">Topic</label>
<input id="topic" name="topic" required maxlength="80">
<button type="submit">Set goal</button>
</form>
<p id="result" role="status"></p>
<script type="module" src="./app.mjs"></script>
</body>
</html>
const form = document.querySelector("#goal-form");
const result = document.querySelector("#result");
if (!(form instanceof HTMLFormElement) || !(result instanceof HTMLElement)) {
throw new Error("Required page elements are missing.");
}
form.addEventListener("submit", event => {
event.preventDefault();
const topic = new FormData(form).get("topic");
if (typeof topic !== "string" || topic.trim() === "") {
result.textContent = "Enter a topic first.";
return;
}
result.textContent = `Next session: ${topic.trim()}`;
});
The form's submit event supports keyboard submission as well as clicking the button. preventDefault stops the ordinary page submission because this exercise handles it in memory. A visible label names the input. The status region announces changes without moving keyboard focus. A successful click alone is therefore not the whole accessibility test: tab through the controls and submit with Enter too.
Render text as text
Use textContent for untrusted plain text, not innerHTML. If someone enters an HTML-looking string, the example should display that string rather than interpreting it as markup. Creating elements explicitly also makes ownership of attributes and text clear. Avoid inline event-handler strings and do not build scripts from user input.
Events have a target, the element where an event originated, and a currentTarget, the element whose listener is currently running. Events may bubble to ancestors, allowing a list container to handle actions for many child buttons. Check that the target is an Element before calling element methods, then resolve the intended action carefully. A button inside a form defaults to submitting it unless its type says otherwise.
Manage lifetime and visible state
Listeners that outlive a component can retain references and cause duplicate work. Remove a listener with the same function reference, or associate supported listeners with an AbortSignal and abort when the component is disposed. For asynchronous work, represent loading, success, empty and error states explicitly. Disable only controls that cannot safely repeat, and avoid moving focus unexpectedly whenever results refresh.
This teaching example stores nothing. Reloading clears its text. Production account data should use an authenticated server API; adding browser storage would introduce a different persistence and privacy contract.
Exercise: add a Clear goal button that never submits the form. Test with keyboard input and a value such as <strong>hello</strong>.
Solution guidance: give the button type button, clear the input and status through properties, and retain a useful focus location. The HTML-looking input must remain visible text rather than becoming a bold element.
Reference: DOM overview.
Previous: Fetch, HTTP Errors and Cancellation. Next: TypeScript Setup and Everyday Types.

