DOM manipulation, insertion and cloning

Insert, wrap, replace and detach nodes with the right method, and understand when a node is moved rather than copied.

Insertion methods and their direction

MethodInsertsTarget-relative form
appendInside, at the endappendTo
prependInside, at the startprependTo
afterAs the next siblinginsertAfter
beforeAs the previous siblinginsertBefore
wrapA new parent around each elementβ€”
wrapAllA single new parent around all of themβ€”
unwrapRemoves the parent, keeps the childrenβ€”
replaceWithReplaces the elementreplaceAll
removeDeletes, discarding data and handlersβ€”
detachDeletes, keeping data and handlersβ€”
// Both directions exist for every insertion; pick the one that reads better.
$('#list').append('<li class="item">New</li>');
$('<li class="item">New</li>').appendTo('#list');       // same result

$('.card').wrap('<div class="card-frame"></div>');       // one wrapper per card
$('.card').wrapAll('<div class="card-row"></div>');      // one wrapper for all cards

// Building DOM instead of HTML strings: safer when values are user-supplied.
const $row = $('<tr>').addClass('row')
  .append($('<td>').text(user.name))                     // text() escapes
  .append($('<td>').text(user.email));
$('tbody').append($row);

// Or use document fragments for a big batch of nodes.
const fragment = document.createDocumentFragment();
users.forEach((user) => {
  const tr = document.createElement('tr');
  tr.className = 'row';
  tr.innerHTML = '<td></td><td></td>';
  tr.children[0].textContent = user.name;
  tr.children[1].textContent = user.email;
  fragment.appendChild(tr);
});
$('#table-body')[0].appendChild(fragment);               // one layout pass
⚠️
Inserting a jQuery object moves the original nodes rather than copying them. $('#a').append($('#b')) relocates #b. This is the intended behaviour and the source of many "my element disappeared" bug reports.

Moving, cloning and comparing nodes

// Moving: the element keeps its identity, handlers and stored data.
$('#panel').append($('#widget'));       // #widget is now inside #panel, not copied

// Cloning: .clone() copies the node; by default handlers and data are NOT copied.
const $copy = $('#template li').clone();                     // markup only
const $live = $('#template li').clone(true);                 // with handlers and data
const $deep = $('#template li').clone(true, true);           // the second flag is deprecated

// Cloning an element with an id creates a duplicate id β€” remove or rewrite it.
$copy.removeAttr('id').attr('data-clone', 'true');

// A template pattern that survives editing: keep the template, clone it.
const template = document.getElementById('row-template').innerHTML;
function renderRow(user) {
  const $row = $(template);
  $row.find('[data-field="name"]').text(user.name);
  $row.find('[data-field="email"]').text(user.email);
  return $row;
}
$('#table-body').append(users.map(renderRow));   // append accepts an array

// Comparing: two jQuery objects are never equal, even for the same element.
$('#a') === $('#a');                    // false β€” new object each call
$('#a')[0] === $('#a')[0];              // true  β€” compare raw elements
$('#a').is($('#b'));                    // true if they share at least one element
$('#a').filter('#b').length > 0;        // equivalent
OperationResultHandlers and data
append an existing elementMovedPreserved
.clone()CopiedNot copied
.clone(true)CopiedCopied
.remove()GoneDiscarded
.detach()Gone from the DOMPreserved
.empty()Children goneChildren's handlers discarded

html, text and val

// Reading
$('#title').html();        // inner HTML string
$('#title').text();        // text content, tags stripped
$('#email').val();         // the current form value (not the attribute)

// Writing
$('#title').html('<em>Draft</em>');    // parses HTML β€” XSS risk with user input
$('#title').text('<em>Draft</em>');    // renders the literal characters, safe
$('#email').val('[email protected]');           // sets the value property
$('#email').val('');                   // clears it

// val() on a select returns the selected value; on a multi-select it returns an array
const selected = $('#tags').val();     // ['red', 'blue']

// A sane default: escape by using .text(), build structure with DOM or jQuery objects.
function setUser(bio) {
  $('#bio').text(bio);                 // never .html() with server data
}

// Attaching many handlers after replacing content: delegate instead (see the events chapter).
$('#results').html(buildHtml(rows));   // if you must use .html(), sanitise first
  • .text() reads the concatenated text of all matched elements; .html() reads the first element's markup only.
  • .val() reads the property, so a value typed by the user is visible even though the value attribute still holds the original.
  • .html() with a string parses and executes nothing, but inline event handler attributes in that string do run β€” treat any interpolated value as untrusted.
  • .empty() removes children and their data but leaves the element itself, which is the right choice for clearing a list before re-rendering.

FAQ

Why did my element vanish when I appended it somewhere else?
Insertion moves existing nodes. If you wanted a copy, call .clone() first. To move and later restore, use .detach() so handlers and data survive the round trip.
Is .html() unsafe?
It parses the string as HTML, so interpolating user data into it is a cross-site scripting hole. Use .text() for values, build elements with jQuery or DOM APIs, and reserve .html() for markup you authored and control.

Selectors and traversal Attributes, properties and the data cache

Last refreshed 2026-09-18.