jQuery / Method / .html() - Read or Replace an Element’s Contents
jQuery's .html() without an argument reads the first selected element's inner HTML string. Passing a string replaces the inner contents of every selected element while keeping the outer elements. To display user input as characters, use .text() instead of interpreting it as HTML with .html().
Reading, writing, text and val
The .html() API returns a string when reading and a jQuery object when writing. Inner HTML is the browser's string representation of the Document Object Model (DOM); do not expect identical whitespace or tag formatting to the original source file.
| Call | Result or operation | Multiple selected elements |
|---|---|---|
| .html() | Inner HTML string including tags | Reads the first only |
| .html(markup) | Replaces contents as HTML | Applies to all |
| .text() | Text without markup | Combines text from all |
| .text(value) | Displays a string as text | Replaces contents of all |
| .val() | Current form-control value | Reads the first control |
.text() does not parse the string as tags. Current input and textarea values are not their inner HTML; use .val(). An empty selection's .html() getter returns undefined. Check that an element exists before immediately calling string methods on the result.
Complete HTML replacement and text display example
Save all the code as UTF-8 in html-demo.html and open it in a browser. Internet is required for the official jQuery file 4.0.0 from its content delivery network (CDN); no package installation is needed. Reading, replacement, input display, reset and the console examples were executed with 3.7.1 and 4.0.0.
The first box contains Alpha in strong; the second contains Beta in em. The report also uses .text(), so retrieved HTML appears as a string instead of being interpreted in the report.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Read and replace HTML contents</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
button, input { font: inherit; margin: 4px; max-width: 100%; }
.box { border: 2px solid teal; padding: 16px; margin: 12px 0; }
pre { white-space: pre-wrap; overflow-wrap: anywhere; }
</style>
</head>
<body>
<h1>HTML markup versus plain text</h1>
<button type="button" id="read">Read</button>
<button type="button" id="replace">Replace with fixed HTML</button>
<label>Message <input id="message" value="<b>Hello</b>"></label>
<button type="button" id="plain">Show message as text</button>
<button type="button" id="reset">Reset</button>
<div id="examples">
<div class="box"><strong>Alpha</strong></div>
<div class="box"><em>Beta</em></div>
</div>
<p>Delegated clicks: <span id="clicks">0</span></p>
<pre id="result" aria-live="polite"></pre>
<script>
$(function () {
const $boxes = $(".box");
const initial = $boxes.get().map(node => node.innerHTML);
let clicks = 0;
function report() {
$("#result").text(`first html=${$boxes.html()}\ncombined text=${$boxes.text()}\nboxes=${$boxes.length}`);
}
$("#read").on("click", report);
$("#replace").on("click", function () {
$boxes.html('<button type="button" class="child">New</button>');
report();
});
$("#plain").on("click", function () {
$boxes.text($("#message").val());
report();
});
$("#examples").on("click.htmlLesson", ".child", function () {
clicks++;
$("#clicks").text(clicks);
});
$("#reset").on("click", function () {
$boxes.html(function (index) { return initial[index]; });
$("#message").val("<b>Hello</b>");
clicks = 0;
$("#clicks").text(clicks);
report();
});
report();
});
</script>
</body>
</html>
Initially, HTML comes from the Alpha box only, while text combines both boxes:
first html=<strong>Alpha</strong>
combined text=AlphaBeta
boxes=2
- Read rereads the current state. first html excludes the enclosing div.
- Replace with fixed HTML replaces both boxes' contents with New buttons. There are still two boxes and combined text is NewNew.
- Each New button increases Delegated clicks. Replacing again still gives one increment per click.
- Show message as text displays
<b>Hello</b>including its tag characters in both boxes. No b element is created. - Edit Message and repeat. This path reads with .val() and passes the value to .text().
- Reset restores each box's saved HTML, the message and zero clicks. It does not register the external delegated handler again.
Keep untrusted input out of HTML insertion
A method that interprets HTML can accept executable attributes or scripts as well as ordinary tags. Passing untrusted input directly into .html() can introduce cross-site scripting (XSS). The replacement button uses only a fixed developer-authored string; Message is handled as text.
Receiving content from a server does not automatically make it trustworthy. For comments, search terms or external data meant as characters, use .text(). If external rich HTML is necessary, design allowed elements/attributes and established sanitization separately. Simple string replacement or removing a few tags is not a complete defense.
Events and data on replaced children
.html(newHtml) replaces existing contents instead of appending. Old child nodes are removed and their jQuery data and direct handlers are cleaned up. Run this complete fragment in the developer tools Console. It creates a temporary container and removes it at the end.
const $host = $('<div><button class="child">Old</button></div>').appendTo("body");
const $old = $host.find("button");
let direct = 0;
let delegated = 0;
$old.data("token", "stored").on("click", () => direct++);
$host.on("click", ".child", () => delegated++);
$host.html('<button class="child">New</button>');
$host.find("button").trigger("click");
console.log(`direct=${direct} | delegated=${delegated}`);
console.log(`old attached=${document.contains($old[0])} | old data=${typeof $old.data("token")}`);
$host.remove();
direct=0 | delegated=1
old attached=false | old data=undefined
The new button has no old direct handler, so direct is zero. The parent remains and its delegated .on() handler handles the new .child, making delegated one. The referenced $old node is detached from the document and its old token data is undefined. Replacing the parent itself would not preserve a handler delegated on that parent.
Use a function for element-specific HTML
.html(function(index, oldHtml)) receives each element's zero-based index and old HTML and returns a new string. Use oldHtml when referring to prior contents inside the callback. This console example appends numbered spans to fixed developer-authored A/B markup.
const $boxes = $("<div><b>A</b></div><div><i>B</i></div>");
$boxes.html(function (index, oldHtml) {
return oldHtml + "<span>" + (index + 1) + "</span>";
});
console.log($boxes.get().map(node => node.innerHTML).join(" | "));
console.log(`empty getter=${typeof $().html()}`);
$boxes.remove();
<b>A</b><span>1</span> | <i>B</i><span>2</span>
empty getter=undefined
oldHtml does not become safe automatically. Use it with known content as here, without extending the pattern by concatenating untrusted input. If nothing changes, check selection scope, target existence, intended inner structure, jQuery loading in Network and Console errors. If you wanted to append, reconsider whether replacing all inner contents with .html() is appropriate.




