Learning Objectives
- Build an element with
createElement, fill it safely, and attach it withappend - Batch many insertions into one live-tree touch with
DocumentFragment - Stamp repeating structure from a
<template>withcloneNode(true) - Place typed markup precisely with
insertAdjacentHTMLand its position keywords
The long way, first
Everything in the demo's board goes through one factory function, and it is worth reading slowly because there is no magic left in it:
Create, configure, connect. Each createElement call makes a detached node — a real element that is simply not in the tree yet — and append wires the pieces together. Note where the user-facing strings go in: textContent, so the values are data, never markup. Verbose? Yes. But every shortcut below is sugar over exactly these moves, and when a shortcut misbehaves, this is the level you debug at.
Batching: one touch, not one hundred
Appending into the live tree makes the browser account for the change — do it in a loop and you pay per iteration. A DocumentFragment is a weightless off-tree container: fill it at leisure, insert it once, and the fragment dissolves, leaving its children behind.
Templates: structure in markup, values in script
When the structure repeats, writing it in JavaScript buries markup in code. The <template> element flips that: inert HTML that the browser parses but neither renders nor runs, waiting to be cloned.
Structure lives where structure belongs, and script only fills in the blanks — through textContent, so user input stays inert. Notice too how the cards' Remove buttons work: one delegated listener on the board, using e.target.closest('button[data-action="remove"]'). That is event delegation paying off exactly as promised — cards created a moment ago are handled by a listener attached before they existed.
If you can type it, you can make it
Sometimes the honest shortcut is to write markup as a string. insertAdjacentHTML is the precise version of that idea: it parses a string and places the result at one of four positions relative to an element — beforebegin, afterbegin, beforeend, afterend.
insertAdjacentHTML runs your string through the HTML parser just like innerHTML does — the injection warning from step 5 applies verbatim. Literal markup you typed: fine. Anything containing outside input: build it the long way or through a template instead.Live demo
All three techniques against one card board: single cards the long way, a hundred at once through a fragment, template stamping with your own title and body, and insertAdjacent* placing paragraphs around the board itself.
Applied
Spoiler toggle. A disclosure widget from almost nothing: content starts with the hidden attribute, and one button flips hidden and mirrors the state into aria-expanded so assistive tech hears what sighted users see. Before building this yourself, ask whether native <details>/<summary> already does the job — the DOM version is for when you need control the native element doesn't give.
Roster builder. Three series' worth of APIs in thirty lines: a form submit event, FormData to read the fields, a template clone filled via textContent, and append onto the roster grid. This is the shape of most small DOM applications you will ever write.
Next Steps
In Step 7: Destruction, the tree shrinks:
- Remove nodes with
remove()and empty containers wholesale - See why deletion loops run bottom-up
- Clean up listeners with
AbortControllerso removed nodes don't leak