jQuery / Change an HTML Tag
To change an HTML tag with jQuery, create an element of the desired type and replace the old element. You cannot rename it by assigning tagName. To retain input values and child handlers, move the actual child nodes into the replacement before removing the original wrapper instead of copying an HTML string.
Renaming a tag versus replacing an element
The DOM, Document Object Model, represents a document as node objects. Element.tagName is read-only. .attr("tagName", "section") does not change an element’s type, and assigning the read-only property through .prop() is not a solution.
Create a section with document.createElement() and replace the div using .replaceWith(). Although the result may look like only a name changed, the outer object is new. Variables that reference the old div do not automatically reference the section.
This exercise compares state during a div-to-section replacement; it is not a complete semantic design for a standalone section. Choose section according to the content’s meaning and heading structure. For a purely visual change, updating a class and CSS may be more suitable.
Complete example that retains child nodes
Save the entire example as UTF-8 tag-demo.html and open it in a browser. Internet loads the official jQuery file 4.0.0. No server or package installation is required. The core behavior was executed in Windows Edge with jQuery 3.7.1 and 4.0.0.
The div contains text, a comment, an input and a button. The button has a direct handler, main has delegation, and the old div has its own handler and jQuery data. Separate counters show which handlers survive.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Replace a container tag, keep its children</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
body { font: 16px/1.6 sans-serif; margin: 20px; max-width: 760px; }
button, input { font: inherit; max-width: 100%; box-sizing: border-box; }
button { margin: 4px; }
.card { border: 3px solid teal; padding: 16px; margin: 16px 0; }
#result { white-space: pre-wrap; overflow-wrap: anywhere; }
</style>
</head>
<body>
<button type="button" id="change">Change div to section</button>
<button type="button" id="reset">Reset</button>
<main id="board"></main>
<pre id="result" aria-live="polite"></pre>
<script>
$(function () {
let original, originalInput, originalNodes;
let childClicks = 0, wrapperClicks = 0, delegatedClicks = 0;
function report() {
const card = document.getElementById("card");
const sameChildren = originalNodes.every((node, i) => card.childNodes[i] === node);
$("#result").text(`tag=${card.localName}\nsame wrapper=${card === original}\nsame children=${sameChildren}\nsame input=${card.querySelector("input") === originalInput}\ninput=${originalInput.value}\nold data=${$(original).data("lesson")}\nchild=${childClicks}; wrapper=${wrapperClicks}; delegated=${delegatedClicks}`);
}
function reset() {
$("#board").empty();
const card = document.createElement("div");
card.id = "card"; card.className = "card";
card.setAttribute("aria-label", "Editable note");
card.append(document.createTextNode("Keep this text. "), document.createComment("keep this comment"));
const input = document.createElement("input");
input.type = "text"; input.value = "First note"; input.setAttribute("aria-label", "Note");
const button = document.createElement("button");
button.type = "button"; button.className = "inspect"; button.textContent = "Inspect";
card.append(input, button); document.getElementById("board").append(card);
original = card; originalInput = input; originalNodes = Array.from(card.childNodes);
childClicks = wrapperClicks = delegatedClicks = 0;
$(card).data("lesson", "old wrapper").on("click", function () { wrapperClicks++; });
$(button).on("click", function () { childClicks++; });
$(input).on("input", report);
$("#change").prop("disabled", false); report();
}
$("#board").on("click.tagLesson", ".inspect", function () { delegatedClicks++; report(); });
$("#change").on("click", function () {
const old = document.getElementById("card");
const replacement = document.createElement("section");
for (const name of ["id", "class", "aria-label"]) {
if (old.hasAttribute(name)) replacement.setAttribute(name, old.getAttribute(name));
}
// Move existing children BEFORE jQuery removes the old wrapper.
$(replacement).append($(old).contents());
$(old).replaceWith(replacement);
$(this).prop("disabled", true); report();
});
$("#reset").on("click", reset);
reset();
});
</script>
</body>
</html>
Initial output follows. same children compares the identity and order of text and comment nodes as well as elements.
tag=div
same wrapper=true
same children=true
same input=true
input=First note
old data=old wrapper
child=0; wrapper=0; delegated=0
- Edit First note. Even
<em>Edited note</em>remains an input value rather than being interpreted as markup. - Press Inspect once: child=1, wrapper=1, delegated=1. The button, original div and main handlers each run.
- Press Change div to section: tag=section, same wrapper=false, same children=true and same input=true. The edited value remains; old data becomes undefined.
- Press Inspect again: child=2, wrapper=1, delegated=2. The child’s direct handler and main’s delegated handler survive. The original div’s handler is not transferred.
- Reset recreates the div, initial value and zero counters. It deliberately discards your edits to establish a fresh exercise state.
What survives and what requires explicit setup
The important order is $(replacement).append($(old).contents()) before replacement. .contents() includes immediate text and comment nodes along with elements. Appending moves these existing objects instead of cloning them, retaining the input’s current value and child handlers. Moving only .children() would omit text and comments outside elements.
After the move, .replaceWith() removes the now-empty div and cleans its jQuery data and handlers. Replacing first and recovering children later can lose their state during cleanup. This is not a universal state-preservation mechanism: review focus, text selection and plugin-held references separately.
The example explicitly copies id, class and aria-label. Other attributes do not transfer automatically. Blind copying may carry invalid attributes, obsolete roles or inline handlers into the new element. Choose necessary attributes and review accessibility meaning. jQuery .data() and HTML data-* attributes are distinct storage mechanisms.
.replaceWith() returns the removed original jQuery collection, not the replacement. Use replacement or select the current element again for subsequent changes. If wrapper behavior is required, bind suitable handlers to the new element or delegate from a stable ancestor as shown.
Limits and troubleshooting
Reading $(old).html() and setting the new element’s .html() reparses a string. The appearance may match, but new child objects are created; existing direct handlers, references and current input state are not thereby preserved. Avoid constructing replacement HTML from user-entered strings.
This is an ordinary HTML-container example, not a universal converter for void elements such as input or img, special table structures, form controls, SVG or custom elements. Check permitted children and semantic consequences first: changing button to a also changes role and keyboard behavior.
If styling changes, inspect tag-dependent CSS such as div.card and selectors that still search for the old tag. Keep id unique and preserve labels and anchors. Verify direct handlers, delegation, focus, accessible meaning and recovery on the actual screen, rather than checking only the input value.




