Topic 8.4
Attributes, Classes and Data
In one line
Attributes are what the HTML says (getAttribute, setAttribute); properties are the live values on the DOM object (input.value); classList adds, removes and toggles classes, and dataset reads and writes your own data-* values.
Think of it like this
A new phone in its box. The sticker on the box says "Colour: blue, Storage: 128 GB, Volume: 50%". That's what the factory wrote, and it never changes. The phone itself has a volume you can turn up and down every day. Look at the sticker and it still says 50%, whatever the phone is set to now. HTML attributes are the sticker: the starting values written in the markup. DOM properties are the phone: the current, live values.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Attribute
- A
name="value"pair written in an HTML tag. Its value is always a string. - Property
- A value on the element's JavaScript object, such as
input.valueorbutton.disabled. It can be any type. - Reflect
- When an attribute and a property are linked, so changing one changes the other (like
id). - Boolean attribute
- An attribute that works by being there or not, like
disabled. Its value is ignored. - classList
- An object on every element with methods to add, remove, toggle and check classes.
- data-* attribute
- An attribute you invent to store your own information on an element, such as
data-price="499". - dataset
- The object that gives you all
data-*attributes of an element, with camelCase names. - camelCase
- Writing joined words with capitals in the middle:
productIdinstead ofproduct-id.
Step by step
01Read and write any attribute
The four basic methods work for every attribute, including ones the browser doesn't know about. They always deal in strings.
const link = document.querySelector("a");
link.getAttribute("href"); // "/shop" (exactly as written)
link.setAttribute("target", "_blank");
link.hasAttribute("target"); // true
link.removeAttribute("target");02Prefer properties for standard things
For known attributes the property is shorter and typed. button.disabled = true is a boolean; setAttribute("disabled", "") does the same with strings.
const button = document.querySelector("button");
button.disabled = true; // adds the disabled attribute
button.title = "Saving..."; // reflects to title="Saving..."
const img = document.querySelector("img");
img.alt = "A blue lamp";03Read what the user typed from the property
The attribute keeps the starting value. The property holds the current one.
04Switch looks with classList
Keep styles in CSS and let JavaScript only switch classes on and off. The CSS for .open decides what "open" looks like.
const menu = document.querySelector(".menu");
const isOpen = menu.classList.toggle("open");
console.log(isOpen ? "Menu opened" : "Menu closed");05Store your own data with data-*
Put the data in the HTML, read it with dataset. Dashes become camelCase, and the values are strings.
<button class="add" data-product-id="42" data-price="499">Add lamp</button>06Turn the strings into real values
dataset.price is the string "499". Doing maths on it without converting is a classic bug: "499" + 1 is "4991" (Topic 1.5).
const btn = document.querySelector(".add");
const id = Number(btn.dataset.productId); // 42
const price = Number(btn.dataset.price); // 499
console.log(price + 1); // 500Try it yourself
- 1
Type, then compare
In the first example, delete the line
nameBox.value = "Ravi";, run, then type something in the box and runconsole.log(nameBox.value, nameBox.getAttribute("value"))from your browser's DevTools console on a real page. Which one changed? - 2
Force a toggle
In the second example, add
btn.classList.toggle("large", 5 > 3);before the last log. Predict the printed element, then run. - 3
Fix the price maths
In the third example, change
item.dataset.price + 1so it prints200.5. There are at least two ways (Number(...)and the unary+).
Code & diagrams
The properties hold the current state; the attributes keep the starting HTML. `disabled="false"` still disabled the button: for boolean attributes only being present counts.
Preview
Console
Nothing logged yet.Expected console output
Ravi Asha Asha
false true
true boolean
false
true number`toggle` returns whether the class is present afterwards. `toggle("primary", true)` left it on because the second argument forces the result. The button is red now because `.danger` replaced `.primary`.
Preview
Console
Nothing logged yet.Expected console output
btn primary large
false
true
false
true
btn danger
<button#btn.btn.danger>`*` turned the string into a number, but `+` joined two strings into `199.51`. Setting `dataset.discountCode` created `data-discount-code`, and the CSS attribute selector turned the item green.
Preview
Console
Nothing logged yet.Expected console output
42 string
true
399 199.51
SAVE10
{ id: '42', price: '199.5', inStock: 'true', discountCode: 'SAVE10' }
falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Turn off a boolean attribute with "false"
Run button.setAttribute("disabled", "false"); to enable a button.
Break #2
Overwrite className
An element has class="card featured". Run card.className = "selected";.
Break #3
Read dataset with the dashed name
For data-product-id="42", write console.log(el.dataset["product-id"]);.
Myth vs fact
Myth
Attributes and properties are the same thing with two syntaxes.
Fact
Many are reflected (linked), but not all, and not always the same way: value and checked separate once the user interacts, href the property is a full URL, and properties have real types while attributes are always strings.
Myth
dataset values keep their types.
Fact
Every data-* value is a string. data-count="3" gives "3", and data-on="false" gives the string "false", which is truthy. Convert explicitly.
Myth
Use data-* for everything, including state like open or disabled.
Fact
When a real attribute exists for the state (disabled, hidden, aria-expanded, aria-pressed), use it, so browsers and screen readers understand the state. data-* is for information only your code needs.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
classListis aDOMTokenList, the same type used byrelon links (link.relList) andsandboxon iframes.togglewith a second argument is the cleanest way to sync a class with a boolean:row.classList.toggle("selected", isSelected). - ▸
Setting an attribute that doesn't affect styling or layout is cheap, but changing a class can invalidate styles for the element and its descendants (and sometimes siblings, with selectors like
.a + .bor:has()). Batching class changes before reading layout matters (Topic 8.8). - ▸
Elements also expose ARIA reflection properties such as
button.ariaPressed = "true"(mirroringaria-pressed), now supported in current browsers;setAttribute("aria-pressed", "true")works everywhere and is still the most common form. - ▸
el.attributesis a liveNamedNodeMapof every attribute, useful for copying or debugging:[...el.attributes].map((a) => a.name + "=" + a.value).el.getAttributeNames()returns just the names as an array.
Remember this
- 1
An attribute is a
name="value"pair written in an HTML tag, liketype="email"orhref="/shop". Attribute values are always strings. Four methods work with any attribute:getAttribute(name)(returns the string, ornullif missing),setAttribute(name, value)(turns the value into a string),hasAttribute(name)andremoveAttribute(name).toggleAttribute(name)adds or removes an attribute that has no value, likehiddenordisabled. Attribute names are not case-sensitive in HTML documents. - 2
A property is a key on the JavaScript object the browser made for the element. Most standard attributes are reflected as properties:
el.id,el.title,link.href,img.alt. Properties have proper types:input.disabledandbox.checkedare booleans,input.maxLengthis a number. Some names differ because they clash with JavaScript keywords: theclassattribute is theclassNameproperty, andfor(on<label>) ishtmlFor. Some properties give a processed value:link.hrefis the full absolute URL, whilelink.getAttribute("href")is exactly what you wrote. - 3
For form fields the two separate. The
valueattribute is the starting value; thevalueproperty is what's in the box now. When a user types, the property changes and the attribute does not.input.defaultValuereflects the attribute. The same goes forcheckedon checkboxes (defaultCheckedis the attribute). So to read what someone typed, always read the property:input.value. This also explains whyinnerHTML(Topic 8.3) loses typed text: the HTML string only contains attributes. - 4
Boolean attributes (
disabled,hidden,checked,required,readonly) work by being present or absent. The value doesn't matter:disabled="false"still disables the button, because the attribute is there. To turn one off, useremoveAttribute("disabled")or set the property:button.disabled = false. - 5
el.classListis the tool for classes.add("a", "b")andremove(...)take one or more names and ignore duplicates or missing ones.toggle("open")adds the class if it's missing and removes it if present, and returnstrueif the class is now there;toggle("open", condition)forces it on when the condition is true and off when it's false.contains("open")checks, andreplace("old", "new")swaps one.el.classNameis the whole class string; assigning to it replaces every class, which can wipe classes other code added. PreferclassList. - 6
Custom data goes in
data-*attributes:<li data-product-id="42" data-in-stock="true">. JavaScript reads them throughel.dataset, where the name afterdata-is turned into camelCase:data-product-idbecomesdataset.productId. Values are always strings, so convert withNumber()or compare with=== "true". Settingel.dataset.color = "red"createsdata-color="red";delete el.dataset.colorremoves it. CSS can read them too:[data-state="open"] { ... }. Usedata-*for your own information, and the real attributes (aria-*,hidden,disabled) when the browser or assistive technology needs to understand the state.
Explain it without notes
What is the difference between an attribute and a property? Give an example where they differ.
How do boolean attributes work, and how do you turn one off from JavaScript?
Why prefer classList over className? What does toggle return?
What are data-* attributes and how does dataset name them?
Practice
A <button data-count="0"> shows a number. Write bump(button) that adds 1 to its data-count, updates the button's text to the new count, and returns the new number.
Write setSelected(row, isSelected) that adds or removes the class selected and sets aria-selected to "true" or "false". Test it on one row.
Trade-offs
- ↔
Keeping state in
data-*attributes and classes makes it visible in DevTools and usable from CSS, but every read is a string parse; for lots of data, keep it in JavaScript objects and only reflect what CSS or assistive technology needs. - ↔
setAttributeworks for every attribute and makes the HTML match, while properties are typed and shorter; mixing them is fine as long as you remember which one holds the current value.
Done when you can
I can use
getAttribute,setAttribute,hasAttribute,removeAttributeandtoggleAttribute.I read form values from properties (
input.value,box.checked), not attributes.I know boolean attributes are on whenever present.
I use
classListto add, remove and toggle classes.I can read and write
data-*values throughdatasetand convert them from strings.