Building pages from data: let a loop write the markup
Five mission cards means the same markup five times. Fifty means you'll make a mistake. Keep the content as data and let a loop produce the markup.
The big idea
Store content as an array of objects, write one function that turns an object into markup, and loop over the array to build the page.
See it in code
An array holds a list; each object in it holds named values. for...of visits each object, and += adds a new piece of markup to what's already there:
<section class="p-6">
<h2 class="text-2xl font-bold">Missions</h2>
<ul id="mission-list" class="mt-3 space-y-1 text-slate-300"></ul>
</section>
<script>
const missions = [
{ name: "Perseverance", kind: "Mars · Rover" },
{ name: "Webb", kind: "Deep space · Telescope" },
{ name: "Juno", kind: "Jupiter · Orbiter" },
];
const list = document.querySelector("#mission-list");
for (const mission of missions) {
list.innerHTML += `<li>${mission.name}</li>`;
}
</script>The ul is empty in the HTML. Text in backticks is a template literal, and ${mission.name} drops a value into it, much like an f-string in Python.
Move the markup into a template function: one object in, one card's markup out. The loop stays tiny however complex the card becomes:
<div id="mission-grid" class="grid grid-cols-3 gap-4 p-6"></div>
<script>
const missions = [
{ name: "Perseverance", kind: "Mars · Rover" },
{ name: "Webb", kind: "Deep space · Telescope" },
{ name: "Juno", kind: "Jupiter · Orbiter" },
];
function missionCard(mission) {
return `
<article class="rounded-2xl border border-white/10 bg-white/5 p-5">
<p class="text-xs uppercase tracking-widest text-violet-300">${mission.kind}</p>
<h3 class="mt-1 text-lg font-bold">${mission.name}</h3>
</article>`;
}
const grid = document.querySelector("#mission-grid");
for (const mission of missions) {
grid.innerHTML += missionCard(mission);
}
</script>The card's design now lives in exactly one place. Restyle it there and every card changes, which is the promise of a component, kept by code.
Data can drive design decisions too. A featured flag picks different classes with the ? : operator, and missions.length keeps a count honest:
<section class="p-6">
<p id="mission-count" class="text-sm font-semibold text-violet-300"></p>
<div id="mission-grid" class="mt-3 grid grid-cols-3 gap-4"></div>
</section>
<script>
const missions = [
{ name: "Juno", kind: "Jupiter · Orbiter", featured: true },
{ name: "Perseverance", kind: "Mars · Rover", featured: false },
{ name: "Webb", kind: "Deep space · Telescope", featured: false },
{ name: "Cassini", kind: "Saturn · Orbiter", featured: false },
];
function missionCard(mission) {
return `
<article class="${mission.featured ? "col-span-2 bg-violet-500/20" : "bg-white/5"} rounded-2xl border border-white/10 p-5">
<p class="text-xs uppercase tracking-widest text-violet-300">${mission.kind}</p>
<h3 class="mt-1 text-lg font-bold">${mission.name}</h3>
</article>`;
}
const grid = document.querySelector("#mission-grid");
for (const mission of missions) {
grid.innerHTML += missionCard(mission);
}
document.querySelector("#mission-count").textContent = missions.length + " missions and counting";
</script>condition ? a : b evaluates to a when the condition is true and b otherwise. To feature a different mission, flip two booleans in the data. No markup changes.
Separating data from presentation is one of the big ideas in software. The same array could feed a table, a chart, or a search index, and none of them would need the others to change.
Try it yourself
Add a fifth mission object and watch the count and the grid update together. Then use missions.filter((mission) => mission.featured) to render only the featured ones.
The common mistake
Writing = where you need +=. grid.innerHTML = missionCard(mission) replaces the grid's contents on every pass, so only the last mission survives. To build up, add on.
What it unlocks
The markup lands in the page through the DOM, and each generated card is a component. The next step is data that isn't typed in at all but arrives from a server: fetching live data.