Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~20 min· topic 4 of 8

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.value or button.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: productId instead of product-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.

app.jswhole filejavascript
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.

app.jswhole filejavascript
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.

Read what the user typed from the propertydiagram
Rendering diagram…

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.

app.jswhole filejavascript
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.

index.htmlwhole filehtml
<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).

app.jswhole filejavascript
const btn = document.querySelector(".add");
const id = Number(btn.dataset.productId);   // 42
const price = Number(btn.dataset.price);    // 499
console.log(price + 1);                     // 500

Try it yourself

  1. 1

    Type, then compare

    In the first example, delete the line nameBox.value = "Ravi";, run, then type something in the box and run console.log(nameBox.value, nameBox.getAttribute("value")) from your browser's DevTools console on a real page. Which one changed?

  2. 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. 3

    Fix the price maths

    In the third example, change item.dataset.price + 1 so it prints 200.5. There are at least two ways (Number(...) and the unary +).

Code & diagrams

Attributes versus properties New tab

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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Ravi Asha Asha
false true
true boolean
false
true number
classList: add, remove, toggle, replace New tab

`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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

btn primary large
false
true
false
true
btn danger
<button#btn.btn.danger>
dataset: your own data on elements New tab

`*` 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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

42 string
true
399 199.51
SAVE10
{ id: '42', price: '199.5', inStock: 'true', discountCode: 'SAVE10' }
false

Break 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.

terminal
$ the page shows
── what you'll see ──
The button stays greyed out and can't be clicked.

Break #2

Overwrite className

An element has class="card featured". Run card.className = "selected";.

terminal
$ the page shows
── what you'll see ──
The card loses its card styling and its featured badge; only .selected rules apply.

Break #3

Read dataset with the dashed name

For data-product-id="42", write console.log(el.dataset["product-id"]);.

terminal
$ Console
── what you'll see ──
undefined

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.

  • ▸

    classList is a DOMTokenList, the same type used by rel on links (link.relList) and sandbox on iframes. toggle with 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 + .b or :has()). Batching class changes before reading layout matters (Topic 8.8).

  • ▸

    Elements also expose ARIA reflection properties such as button.ariaPressed = "true" (mirroring aria-pressed), now supported in current browsers; setAttribute("aria-pressed", "true") works everywhere and is still the most common form.

  • ▸

    el.attributes is a live NamedNodeMap of 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. 1

    An attribute is a name="value" pair written in an HTML tag, like type="email" or href="/shop". Attribute values are always strings. Four methods work with any attribute: getAttribute(name) (returns the string, or null if missing), setAttribute(name, value) (turns the value into a string), hasAttribute(name) and removeAttribute(name). toggleAttribute(name) adds or removes an attribute that has no value, like hidden or disabled. Attribute names are not case-sensitive in HTML documents.

  2. 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.disabled and box.checked are booleans, input.maxLength is a number. Some names differ because they clash with JavaScript keywords: the class attribute is the className property, and for (on <label>) is htmlFor. Some properties give a processed value: link.href is the full absolute URL, while link.getAttribute("href") is exactly what you wrote.

  3. 3

    For form fields the two separate. The value attribute is the starting value; the value property is what's in the box now. When a user types, the property changes and the attribute does not. input.defaultValue reflects the attribute. The same goes for checked on checkboxes (defaultChecked is the attribute). So to read what someone typed, always read the property: input.value. This also explains why innerHTML (Topic 8.3) loses typed text: the HTML string only contains attributes.

  4. 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, use removeAttribute("disabled") or set the property: button.disabled = false.

  5. 5

    el.classList is the tool for classes. add("a", "b") and remove(...) 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 returns true if 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, and replace("old", "new") swaps one. el.className is the whole class string; assigning to it replaces every class, which can wipe classes other code added. Prefer classList.

  6. 6

    Custom data goes in data-* attributes: <li data-product-id="42" data-in-stock="true">. JavaScript reads them through el.dataset, where the name after data- is turned into camelCase: data-product-id becomes dataset.productId. Values are always strings, so convert with Number() or compare with === "true". Setting el.dataset.color = "red" creates data-color="red"; delete el.dataset.color removes it. CSS can read them too: [data-state="open"] { ... }. Use data-* 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

01

What is the difference between an attribute and a property? Give an example where they differ.

02

How do boolean attributes work, and how do you turn one off from JavaScript?

03

Why prefer classList over className? What does toggle return?

04

What are data-* attributes and how does dataset name them?

Practice

01

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.

02

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.

  • ↔

    setAttribute works 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, removeAttribute and toggleAttribute.

  • I read form values from properties (input.value, box.checked), not attributes.

  • I know boolean attributes are on whenever present.

  • I use classList to add, remove and toggle classes.

  • I can read and write data-* values through dataset and convert them from strings.