Attributes, Properties & Data
Elements carry information in three places: attributes written in the HTML, properties on the live DOM object, and data you attach for your own scripts. jQuery gives you .attr(), .prop() and .data() for these, and mixing them up is behind many confusing bugs, especially with checkboxes and data-* values.
This lesson explains each one with a clear rule for when to use it, and shows the plain JavaScript versions: getAttribute(), ordinary properties and dataset.
Attributes with attr()
An attribute is a name="value" pair in the HTML, such as href="/fees" or alt="Logo". Attributes are always strings.
$("#logo").attr("alt"); // read
$("#logo").attr("alt", "Rohtak Public School"); // write
$("#logo").attr({ width: 120, height: 40 }); // write several at once
$("#logo").removeAttr("title"); // delete
// Plain JavaScript
const logo = document.querySelector("#logo");
logo.getAttribute("alt");
logo.setAttribute("alt", "Rohtak Public School");
logo.removeAttribute("title");
logo.toggleAttribute("hidden", true); // add when true, remove when falseIn jQuery 4, passing false as the value removes the attribute, except for ARIA attributes. .attr("aria-expanded", false) sets the text "false", which is exactly what screen readers need.
Properties with prop()
A property belongs to the DOM object that the browser builds from the HTML. Many properties start from an attribute but then live their own life. The classic example is a checkbox:
- The
checkedattribute says whether the box was ticked when the page loaded. It does not change when the user clicks. - The
checkedproperty says whether the box is ticked right now.
Run this and tick or untick the box a few times:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>attr vs prop</title>
</head>
<body>
<label><input type="checkbox" id="sms" checked> Send me SMS alerts</label>
<p><a id="fees" href="fees.html">Fee structure</a></p>
<pre id="out"></pre>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
function report() {
const $box = $("#sms");
$("#out").text(
"checked attribute present: " + $box.is("[checked]") + "\n" +
"checked property: " + $box.prop("checked") + "\n" +
"href attribute: " + $("#fees").attr("href") + "\n" +
"href property: " + $("#fees").prop("href")
);
}
$("#sms").on("change", report);
report();
</script>
</body>
</html>The attribute line never changes, but the property follows your clicks. The link shows a second difference: the href attribute is exactly what you wrote, while the href property is the full, resolved address.
Rule of thumb: use .prop() (or plain properties) for live state such as checked, selected, disabled and value; use .attr() for everything else, especially custom and ARIA attributes.
| Task | jQuery | Plain JavaScript |
|---|---|---|
| Is the box ticked? | $cb.prop("checked") or $cb.is(":checked") | cb.checked |
| Disable a button | $btn.prop("disabled", true) | btn.disabled = true |
| Read an image's alt text | $img.attr("alt") | img.getAttribute("alt") or img.alt |
| Mark a menu as open | $btn.attr("aria-expanded", "true") | btn.setAttribute("aria-expanded", "true") |
Custom data with data-* attributes
HTML lets you invent attributes that start with data-, such as data-price="1299". They are the standard place to keep information your script needs about an element.
jQuery's .data() reads them, but with two surprises:
- It converts types.
"1299"becomes the number1299,"true"becomestrue, and JSON text becomes an object or array. A value is converted to a number only if that does not change how it looks, so"007"and"1.50"stay strings. - It reads once, then caches. The first
.data()call copies the attributes into jQuery's internal storage. After that,.data("price", 999)updates only that storage, not the HTML attribute, and later changes to the attribute are not seen by.data().
The plain dataset property is simpler: it always reads and writes the real attribute, and every value is a string.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>data() vs dataset</title>
</head>
<body>
<div id="phone" data-price="12999" data-in-stock="true" data-colours='["Black","Blue"]' data-code="007">
Smartphone 5G
</div>
<button id="discount">Apply ₹1,000 discount with .data()</button>
<pre id="out"></pre>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const $phone = $("#phone");
const phone = document.querySelector("#phone");
function show() {
const d = $phone.data();
$("#out").text(
"jQuery .data():\n" +
" price " + d.price + " (" + typeof d.price + ")\n" +
" inStock " + d.inStock + " (" + typeof d.inStock + ")\n" +
" colours " + d.colours.join(" & ") + " (array: " + Array.isArray(d.colours) + ")\n" +
" code " + d.code + " (" + typeof d.code + ")\n\n" +
"Plain dataset:\n" +
" price " + phone.dataset.price + " (" + typeof phone.dataset.price + ")\n" +
" inStock " + phone.dataset.inStock + " (" + typeof phone.dataset.inStock + ")\n\n" +
"HTML attribute: data-price=\"" + phone.getAttribute("data-price") + "\""
);
}
$("#discount").on("click", function () {
$phone.data("price", $phone.data("price") - 1000);
show();
});
show();
</script>
</body>
</html>After clicking, .data() shows the new price but the HTML attribute still says 12999. If other code (or your CSS, with [data-price] selectors) reads the attribute, it sees the old value.
Choosing between data() and dataset
- Use
.data()in jQuery code when you want typed values or need to store objects, such as a whole product record, on an element without putting it in the HTML. - Use
.attr("data-x", value)ordatasetwhen the value must appear in the HTML, for example to style with[data-status="paid"]in CSS. - Do not mix both styles for the same key. Pick one per value.
// Store a full object on an element (jQuery only keeps it in memory)
$("#order-1042").data("order", { id: 1042, city: "Kochi", items: 3 });
$("#order-1042").data("order").city; // "Kochi"
// Plain JavaScript equivalent: a Map keyed by element
const orders = new Map();
orders.set(document.querySelector("#order-1042"), { id: 1042, city: "Kochi", items: 3 });Common mistakes
.attr("checked")to test a checkbox. It reflects the starting HTML, not the current state. Use.prop("checked").- Expecting
.data("x", v)to change the HTML. It does not; use.attr("data-x", v)if you need the attribute. - Doing maths on
datasetvalues. They are strings:"12999" + 1is"129991". Convert withNumber(). - Expecting
"007"to become a number with.data(). jQuery keeps it as a string because converting would change it. - Writing
data-inStockin HTML. Attribute names are lowercase; writedata-in-stockand read it asinStock.
Key points
- Attributes are the HTML text (
.attr(),getAttribute()); properties are the live DOM state (.prop(),el.checked). - Use
.prop()forchecked,selected,disabledandvalue;.attr()for custom and ARIA attributes. - In jQuery 4,
.attr(name, false)removes a normal attribute but writes"false"for ARIA attributes. .data()converts types and caches values without updating the HTML;datasetalways uses strings and the real attribute.data-in-stockin HTML becomesinStockin both.data()anddataset.
Exercise
Create a "Book a seat" button with data-seats-left="3" and an aria-live paragraph. Each click should reduce the seat count by one using .attr() to read and write the data-seats-left attribute (so the HTML stays in sync), update the paragraph, and when it reaches 0, disable the button with .prop().
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Seats</title>
</head>
<body>
<button id="book" data-seats-left="3">Book a seat</button>
<p id="msg" aria-live="polite">3 seats left in the Jaipur workshop.</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
$("#book").on("click", function () {
const left = Number($(this).attr("data-seats-left")) - 1;
$(this).attr("data-seats-left", left);
$("#msg").text(left + " seats left in the Jaipur workshop.");
if (left === 0) {
$(this).prop("disabled", true).text("Fully booked");
}
});
</script>
</body>
</html>Reading with .attr() gives a string, so Number() converts it before subtracting; writing back with .attr() keeps the HTML attribute up to date.
Quick Quiz
1. A user unticks a checkbox that had checked in the HTML. What does .prop("checked") return?
The property tracks the current state; only the attribute still remembers the starting HTML.
2. What does $("#d").data("code") return for data-code="007"?
jQuery converts to a number only when that would not change the text, so leading zeros keep it a string.
3. After $el.data("price", 500), what does $el.attr("data-price") return if the HTML said data-price="800"?
.data() writes to jQuery's own storage, not to the attribute.
4. Which plain JavaScript line disables a button?
The disabled property is the live state; note that any disabled attribute, even with the value "false", disables the button.
Learn jQuery with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
