Himanshu SharmaSoftware engineer
Let’s talk

jQuery · Lesson 20 of 45

Wrapping and cloning elements

Wrap elements in new containers with jQuery .wrap(), .wrapAll() and .wrapInner(), remove wrappers with .unwrap(), and copy elements with .clone().

10 minAdding and removing elementsUpdated 23 October 2026

By the end of this lessonAdd or remove container elements around existing content, and duplicate elements with or without their event handlers.

Adding a container

Sometimes the HTML you are given is missing a wrapper you need for styling or layout, for example an image without a figure, or a table without a scrolling container. The wrapping methods add one.

html
<style>
  .frame { display: inline-block; padding: 6px; border: 2px solid #94a3b8; margin: 4px; }
  .gallery { background: #f1f5f9; padding: 8px; }
</style>
<span class="pic">Photo 1</span>
<span class="pic">Photo 2</span>
<span class="pic">Photo 3</span>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $(".pic").wrap("<span class='frame'></span>");
  console.log("frames:", $(".frame").length);

  $(".frame").wrapAll("<div class='gallery'></div>");
  console.log("galleries:", $(".gallery").length, "| frames inside:", $(".gallery .frame").length);
</script>
result
frames: 3
galleries: 1 | frames inside: 3
  • .wrap(html) gives each element its own new parent: three photos, three frames.
  • .wrapAll(html) puts one parent around the whole selection. (If the elements are in different places, jQuery moves them next to the first one.)

The wrapper can be an HTML string, a jQuery object or an existing element. When you pass an existing element, jQuery uses a copy of it as the wrapper, so the original stays where it is. If the wrapper HTML is nested, such as <div><span></span></div>, the selected element is placed inside the innermost element.

Wrapping the contents

.wrapInner() wraps what is inside each element instead of the element itself:

html
<p class="tip">Save often.</p>
<p class="tip">Use keyboard shortcuts.</p>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $(".tip").wrapInner("<strong></strong>");
  console.log($(".tip").first().html());
</script>
result
<strong>Save often.</strong>

A common real use is making long tables scroll on phones without touching the server-side HTML:

javascript
$("table.wide").wrap("<div class='table-scroll'></div>");

with .table-scroll { overflow-x: auto; } in your CSS.

Removing a container

.unwrap() removes the parent of each selected element and keeps the element where it was:

html
<div class="old-wrapper"><p id="text">Keep me</p></div>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $("#text").unwrap();
  console.log("wrapper gone:", $(".old-wrapper").length === 0, "| text still here:", $("#text").length === 1);
</script>
result
wrapper gone: true | text still here: true

To remove a tag but keep its contents, unwrap the contents. .contents() selects every child node, including bare text:

html
<p>This is <b>very</b> important.</p>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $("b").contents().unwrap();
  console.log($("p").html());
</script>
result
This is very important.

Copying with .clone()

.clone() makes a deep copy of the selected elements, including everything inside them. The copy is not on the page until you insert it:

html
<table id="items" border="1" cellpadding="4">
  <tr class="row"><td><input name="item[]" placeholder="Item"></td><td><input name="qty[]" value="1" size="3"></td></tr>
</table>
<button id="add-row">Add another row</button>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $("#add-row").on("click", function () {
    const $copy = $("#items .row").first().clone();
    $copy.find("input").val(function () { return this.name === "qty[]" ? "1" : ""; });
    $copy.appendTo("#items");
    console.log("rows:", $("#items .row").length);
  });
</script>
result
rows: 2
rows: 3

Cloning the first row and clearing its inputs is the classic way to add repeatable fields to an order or invoice form. Remember to reset values in the copy, or the user's typing is copied too.

Copying behaviour too

A plain .clone() copies the HTML, not the jQuery event handlers or data. Pass true to copy those as well:

html
<button class="like">Like</button>
<div id="more"></div>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  let likes = 0;
  $(".like").on("click", () => likes++);

  $(".like").first().clone().appendTo("#more");        // HTML only
  $(".like").first().clone(true).appendTo("#more");    // with handlers

  $("#more .like").each(function () { $(this).trigger("click"); });
  console.log("likes counted:", likes, "of", $("#more .like").length, "clones clicked");
</script>
result
likes counted: 1 of 2 clones clicked

Only the clone made with clone(true) responded. In most new code, delegated events (covered in the events section) make clone(true) unnecessary, because a single handler on the container works for every row, old or new.

Duplicate ids

If the element you clone has an id, the copy has the same id. That breaks $("#id"), which returns only the first match, and <label for>, which points at one element. Remove or change ids on the copy:

javascript
$copy.removeAttr("id");
$copy.find("[id]").each(function () { this.id = this.id + "-" + rowNumber; });

The same applies to form field names in some server frameworks. PHP, for example, collects fields named item[] into an array, which is why the order form above uses square brackets: every cloned row adds another entry, and the server receives all of them. A name without brackets would send only the last row.

Common mistakes

  • Using .wrap() when you mean .wrapAll(), getting many containers instead of one.
  • Forgetting to clear values in cloned form rows.
  • Leaving duplicate ids in clones.
  • Expecting a plain .clone() to copy handlers.

Exercise

Wrap every img on a page in a <figure> and add a <figcaption> after each image that shows the image's alt text.

Show one possible answer
html
<img src="" alt="A lake at dawn">
<img src="" alt="A mountain path">

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  $("img").wrap("<figure></figure>").each(function () {
    $(this).after($("<figcaption>", { text: $(this).attr("alt") }));
  });
  console.log($("figure").length, $("figcaption").text());
</script>
result
2 A lake at dawnA mountain path

.wrap() returns the original images, so the chain continues on them.

Key points

  • .wrap() puts a new parent around each selected element; .wrapAll() puts one parent around all of them.
  • .wrapInner() wraps the contents inside each element; .unwrap() removes the parent and keeps the element.
  • .clone() copies elements; .clone(true) also copies their jQuery data and event handlers.
  • Change ids on cloned elements, because an id must be unique on the page.

Check yourself

1. What does $("img").wrap("<figure>") do with three images?
2. What does .unwrap() remove?
3. Does .clone() copy click handlers attached with .on()?
4. Why must you change the id of a cloned element?