How to Dynamically Create and Append DOM Elements in JavaScript: A Complete Guide

You can dynamically create and append DOM elements in JavaScript by using document.createElement() to instantiate nodes, configuring properties like className and innerText, and inserting them into the document tree with appendChild(), append(), or insertBefore().

The jisan-mia/dom-projects repository demonstrates production-ready patterns for dynamically creating and appending DOM elements in JavaScript across multiple real-world applications. Each project follows a consistent architectural approach: instantiate elements, configure attributes and content, attach event listeners, and append to parent containers.

The Core Pattern for Creating DOM Elements Dynamically

Every dynamic UI component in the repository follows a four-step workflow. This pattern ensures clean separation between data, presentation, and interaction logic.

Step 1: Instantiate the Element

Use document.createElement() with the tag name to create a new node detached from the document.

const card = document.createElement('div');
const title = document.createElement('h2');
const image = document.createElement('img');

Step 2: Configure Properties and Attributes

Set className, innerText, src, or use setAttribute() for custom data attributes.

card.className = 'project-card';
title.innerText = 'My Project';
image.src = './assets/preview.png';
image.alt = 'Project preview';
card.setAttribute('data-project-id', '123');

Step 3: Assemble the Component Tree

Append child elements to their parent container using appendChild() or the modern append() method.

card.appendChild(title);
card.appendChild(image);
// Or append multiple nodes at once:
card.append(title, image, description);

Step 4: Insert into the Document

Attach the completed component to an existing element in the DOM.

document.querySelector('.project-list-area').appendChild(card);

Real-World Implementation Examples from dom-projects

The repository contains multiple implementations demonstrating how to dynamically create and append DOM elements in JavaScript across different complexity levels.

Building Project Cards in main/index.js

The home page dynamically generates project cards from JSON data. In main/index.js, the code creates container elements, configures image and text content, and appends them to the .project-list-area container.

const projectCard = document.createElement("div");
projectCard.className = "project-card";

const projectImg = document.createElement("img");
projectImg.src = project.image;
projectImg.alt = project.name;

const projectContent = document.createElement("div");
projectContent.className = "project-content";

// Assemble and append
projectCard.append(projectImg, projectContent);
document.querySelector('.project-list-area').appendChild(projectCard);

Helper functions like getProjectImg(), createAnchorElm(), and getProjectTags() encapsulate specific element creation logic, keeping the main rendering code clean.

Generating Todo List Items in projects/Todo-List-application/script.js

The basic todo application demonstrates dynamic list item creation. When users add a new task, projects/Todo-List-application/script.js instantiates an <li> element, sets its text content, and attaches event listeners before appending to the list.

function addTodo(text, completed = false) {
  const todoEl = document.createElement('li');
  todoEl.innerText = text;
  
  if (completed) {
    todoEl.classList.add('completed');
  }
  
  // Event wiring after creation
  todoEl.addEventListener('click', () => {
    todoEl.classList.toggle('completed');
  });
  
  todoEl.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    todoEl.remove();
  });
  
  document.getElementById('todos').appendChild(todoEl);
}

Using HTML Templates for Reusable Components in projects/advanced-todo/script.js

For complex components, projects/advanced-todo/script.js demonstrates the <template> element approach. This method defines the HTML structure statically in the document, then clones it dynamically for each instance.

<template id="todo-template">
  <li class="list-item">
    <input type="checkbox" data-list-item-checkbox>
    <span data-list-item-text></span>
    <button class="edit-todo">✏️</button>
    <button class="delete-todo">🗑️</button>
  </li>
</template>
function renderTodo(todo) {
  const template = document.getElementById('todo-template');
  const clone = template.content.cloneNode(true);
  
  const listItem = clone.querySelector('.list-item');
  listItem.dataset.todoId = todo.id;
  
  clone.querySelector('[data-list-item-text]').innerText = todo.name;
  clone.querySelector('[data-list-item-checkbox]').checked = todo.isCompleted;
  
  // Append the fragment to the live DOM
  document.querySelector('.list').appendChild(clone);
}

This pattern improves maintainability by keeping markup out of JavaScript strings and allows CSS styling of the template structure in the HTML file.

Advanced Techniques and Best Practices

Separating Concerns with Helper Functions

The repository demonstrates clean architecture by isolating element creation into specialized functions. In main/index.js, functions like createAnchorElm() and getProjectTags() handle specific DOM generation tasks, keeping the main rendering logic readable and testable.

function createAnchorElm(href, text, className) {
  const a = document.createElement('a');
  a.href = href;
  a.innerText = text;
  a.className = className;
  return a;
}

Event Delegation vs. Direct Attachment

For simple components like the basic todo app, event listeners attach directly to dynamically created elements immediately after creation. However, for high-frequency updates or large lists, consider event delegation by attaching a single listener to the parent container and using event.target to identify which child was clicked.

Performance Considerations

When creating many elements, minimize reflows by using DocumentFragment or by detaching the parent container during bulk insertion. The projects/advanced-todo/script.js file uses template cloning which naturally returns a DocumentFragment, allowing efficient batch appending to the live DOM.

Complete Code Examples

Simple Card Builder

function createCard(data) {
  const card = document.createElement('div');
  card.className = 'card';

  const title = document.createElement('h2');
  title.innerText = data.title;
  card.appendChild(title);

  const img = document.createElement('img');
  img.src = data.image;
  img.alt = data.title;
  card.appendChild(img);

  const desc = document.createElement('p');
  desc.innerText = data.description;
  card.appendChild(desc);

  document.querySelector('.cards-container').appendChild(card);
}

Dynamic Todo Item with Events

function addTodo(text, completed = false) {
  const li = document.createElement('li');
  li.innerText = text;
  
  if (completed) li.classList.add('completed');

  li.addEventListener('click', () => li.classList.toggle('completed'));
  
  li.addEventListener('contextmenu', e => {
    e.preventDefault();
    li.remove();
  });

  document.getElementById('todos').appendChild(li);
}

Template-Based Component Rendering

<template id="todo-template">
  <li class="list-item">
    <input type="checkbox" data-list-item-checkbox>
    <span data-list-item-text></span>
    <button class="edit-todo">✏️</button>
    <button class="delete-todo">🗑️</button>
  </li>
</template>
function renderTodo(todo) {
  const tmpl = document.getElementById('todo-template');
  const clone = tmpl.content.cloneNode(true);

  const li = clone.querySelector('.list-item');
  li.dataset.todoId = todo.id;

  clone.querySelector('[data-list-item-text]').innerText = todo.name;
  clone.querySelector('[data-list-item-checkbox]').checked = todo.isCompleted;

  document.querySelector('.list').appendChild(clone);
}

Summary

  • Use document.createElement() to instantiate new DOM nodes before attaching them to the document.
  • Configure elements by setting className, innerText, src, and custom data attributes before insertion.
  • Assemble components by appending child elements using appendChild() or the modern append() method.
  • Insert into the live DOM only after the element tree is fully constructed to minimize reflows.
  • Leverage HTML templates via <template> elements and cloneNode(true) for maintainable, reusable component structures.
  • Encapsulate creation logic in helper functions like createAnchorElm() or getProjectTags() to keep rendering code clean and testable.

Frequently Asked Questions

What is the difference between append() and appendChild()?

appendChild() is the legacy method that accepts a single Node object and returns the appended child. append() is the modern standard that accepts multiple arguments (Nodes or strings) and returns undefined. Use append() when you need to insert multiple elements or text nodes at once, as shown in main/index.js where projectCard.append(projectImg, projectContent, projectLinks) attaches multiple components simultaneously.

How do I add event listeners to dynamically created elements?

Attach event listeners immediately after creating the element and before appending it to the DOM. In projects/Todo-List-application/script.js, the code calls todoEl.addEventListener('click', …) right after document.createElement('li') but before todosUL.appendChild(todoEl). This ensures the element is interactive as soon as it appears on the page. For large lists, consider event delegation on the parent container instead.

Is it better to use innerHTML or createElement for dynamic content?

Use document.createElement() when you need to attach event listeners, maintain references to specific nodes, or avoid XSS vulnerabilities from user-generated content. The jisan-mia/dom-projects repository exclusively uses createElement and template cloning because it allows direct event wiring and safer handling of dynamic data. Reserve innerHTML only for static HTML strings where no further JavaScript interaction is required.

How can I improve performance when creating many DOM elements?

Minimize reflows by using DocumentFragment or by detaching the parent container during bulk insertion. In projects/advanced-todo/script.js, cloning a <template> returns a DocumentFragment that appends to the live DOM in one operation. Additionally, batch your DOM reads and writes—create all elements in memory first, then append them in a single operation rather than interleaving creation and insertion.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →