How to Work with DOM Attributes and Properties in JavaScript
When the browser parses HTML, it creates DOM nodes for every element. Each DOM node is a JavaScript object, and like any object, it has properties. At the same time, the HTML tags that generated those nodes had attributes. These two concepts (attributes and properties) are related but not the same thing. They often overlap, sometimes stay in sync, and sometimes diverge in ways that cause confusing bugs.
Understanding the difference between HTML attributes and DOM properties is essential for writing correct DOM manipulation code. This guide explains how they relate, when they synchronize, when they do not, how to work with custom data attributes using dataset, and why property types matter more than most developers realize.
DOM Properties: Object Properties on DOM Nodes
When the browser creates a DOM element from an HTML tag, it creates a JavaScript object. That object has properties defined by its class in the DOM hierarchy. These are regular JavaScript object properties that you can read, write, and even add custom ones to.
Standard Properties Come from the Class
Each type of HTML element has a corresponding DOM class. The properties defined by that class become the DOM element's properties:
let input = document.createElement("input");
// Properties defined by HTMLInputElement
console.log(input.type); // "text" (default)
console.log(input.value); // ""
console.log(input.checked); // false
console.log(input.disabled); // false
let link = document.createElement("a");
// Properties defined by HTMLAnchorElement
console.log(link.href); // ""
console.log(link.target); // ""
console.log(link.protocol); // ":"
These properties behave like regular JavaScript object properties:
let input = document.querySelector("input");
// Read
console.log(input.value); // current value
// Write
input.value = "Hello";
console.log(input.value); // "Hello"
// Check existence
console.log("value" in input); // true
console.log("type" in input); // true
You Can Add Custom Properties to DOM Objects
Because DOM elements are regular JavaScript objects, you can attach your own properties to them. This is completely valid JavaScript, though not always the best practice:
let div = document.querySelector("div");
// Adding custom properties
div.myData = { count: 42, label: "clicks" };
div.greet = function() {
console.log("Hello from this div!");
};
console.log(div.myData.count); // 42
div.greet(); // "Hello from this div!"
While adding custom properties to DOM elements works, it is generally discouraged for several reasons:
- It can conflict with future DOM API additions (if the spec adds a property with the same name).
- It is invisible in HTML and DevTools attribute panels.
- It does not survive
innerHTMLserialization/deserialization.
For attaching custom data to elements, use data-* attributes and the dataset property (covered later in this guide).
Exploring an Element's Properties
You can see all properties of a DOM element using console.dir or by iterating:
let div = document.querySelector("div");
// See the object with all its properties
console.dir(div);
// List property names from the prototype chain
let props = [];
let obj = div;
while (obj) {
props.push(...Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
}
console.log(`Total properties: ${props.length}`);
// Typically 200+ properties on an HTMLDivElement
HTML Attributes: getAttribute, setAttribute, removeAttribute, hasAttribute
HTML attributes are the name-value pairs you write in your HTML markup. They exist in the HTML source and are managed through a dedicated API that is separate from DOM properties.
Reading Attributes
<a id="link" href="https://example.com" target="_blank" class="nav-link">
Example
</a>
let link = document.getElementById("link");
// getAttribute reads the HTML attribute value as a string
console.log(link.getAttribute("href")); // "https://example.com"
console.log(link.getAttribute("target")); // "_blank"
console.log(link.getAttribute("class")); // "nav-link"
console.log(link.getAttribute("id")); // "link"
// Returns null for attributes that don't exist
console.log(link.getAttribute("title")); // null
console.log(link.getAttribute("data-x")); // null
Setting Attributes
let link = document.getElementById("link");
// setAttribute sets (or creates) an HTML attribute
link.setAttribute("title", "Visit Example");
link.setAttribute("rel", "noopener");
// The attribute now appears in the HTML
console.log(link.getAttribute("title")); // "Visit Example"
console.log(link.getAttribute("rel")); // "noopener"
// You can also modify existing attributes
link.setAttribute("href", "https://newsite.com");
console.log(link.getAttribute("href")); // "https://newsite.com"
Checking for Attributes
let link = document.getElementById("link");
console.log(link.hasAttribute("href")); // true
console.log(link.hasAttribute("target")); // true
console.log(link.hasAttribute("title")); // true (we just set it)
console.log(link.hasAttribute("data-id")); // false
Removing Attributes
let link = document.getElementById("link");
link.removeAttribute("target");
console.log(link.hasAttribute("target")); // false
console.log(link.getAttribute("target")); // null
// Removing a non-existent attribute does nothing (no error)
link.removeAttribute("nonexistent"); // No error
Attribute Names Are Case-Insensitive
HTML attribute names are case-insensitive. The browser stores them in lowercase:
let div = document.querySelector("div");
div.setAttribute("MyAttribute", "hello");
// All of these return the same value
console.log(div.getAttribute("myattribute")); // "hello"
console.log(div.getAttribute("MyAttribute")); // "hello"
console.log(div.getAttribute("MYATTRIBUTE")); // "hello"
Attribute Values Are Always Strings
No matter what you set, attribute values are stored and returned as strings:
let div = document.querySelector("div");
div.setAttribute("data-count", 42);
console.log(div.getAttribute("data-count")); // "42" (string, not number)
console.log(typeof div.getAttribute("data-count")); // "string"
div.setAttribute("data-active", true);
console.log(div.getAttribute("data-active")); // "true" (string, not boolean)
console.log(typeof div.getAttribute("data-active")); // "string"
Attribute-Property Synchronization (and When It Breaks)
Here is where things get nuanced. When the browser parses HTML and creates a DOM element, it reads the HTML attributes and uses them to initialize corresponding DOM properties. For standard attributes, there is often a link between the attribute and the property, but that link is not always bidirectional, and it does not always stay in sync.
Standard Attributes Create Properties
For standard HTML attributes, the browser creates a corresponding DOM property:
<input id="name" type="text" value="Alice" class="form-input">
let input = document.getElementById("name");
// HTML attributes → DOM properties (initial sync)
console.log(input.type); // "text" (from type="text")
console.log(input.value); // "Alice" (from value="Alice")
console.log(input.className); // "form-input" (from class="form-input")
console.log(input.id); // "name" (from id="name")
Two-Way Sync for Most Properties
For most standard attributes, changes to the property update the attribute, and changes to the attribute update the property:
let input = document.getElementById("name");
// Change the property → attribute updates
input.id = "username";
console.log(input.getAttribute("id")); // "username" ✅ synced
// Change the attribute → property updates
input.setAttribute("id", "email");
console.log(input.id); // "email" ✅ synced
The value Exception: One-Way Sync
The most important exception to two-way synchronization is the input.value property. The synchronization is one-way only: attribute to property, but not property to attribute.
<input id="name" type="text" value="Alice">
let input = document.getElementById("name");
// Initially they are the same
console.log(input.value); // "Alice"
console.log(input.getAttribute("value")); // "Alice"
// Attribute → Property: works
input.setAttribute("value", "Bob");
console.log(input.value); // "Bob" ✅
// Property → Attribute: does NOT sync
input.value = "Charlie";
console.log(input.value); // "Charlie"
console.log(input.getAttribute("value")); // "Bob" ❌ still "Bob"!
This behavior exists by design. The HTML value attribute represents the initial/default value, while the DOM value property represents the current value. This lets you always retrieve the original value using getAttribute("value") or input.defaultValue, even after the user has changed the input.
let input = document.getElementById("name");
// User types "Charlie" into the input field
// or: input.value = "Charlie";
console.log(input.value); // "Charlie" (current value)
console.log(input.defaultValue); // "Alice" (original value (from HTML attribute))
// Reset the input to its original value
input.value = input.defaultValue; // Back to "Alice"
The href Attribute vs. Property
Another notable difference occurs with href. The property returns the full resolved URL, while the attribute returns exactly what was written in the HTML:
<a id="link" href="/about">About</a>
let link = document.getElementById("link");
// Property: full absolute URL
console.log(link.href); // "https://example.com/about"
// Attribute: exactly what's in the HTML
console.log(link.getAttribute("href")); // "/about"
This distinction matters when you need the raw value the developer wrote, not the browser-resolved version:
// Checking if a link is relative or absolute
let rawHref = link.getAttribute("href");
if (rawHref.startsWith("/")) {
console.log("Relative URL");
} else if (rawHref.startsWith("http")) {
console.log("Absolute URL");
} else if (rawHref.startsWith("#")) {
console.log("Hash link");
}
The class Attribute vs. className Property
The HTML attribute is named class, but the DOM property is named className (because class is a reserved word in JavaScript):
<div id="box" class="card active">Content</div>
let box = document.getElementById("box");
// Property name is className (not class)
console.log(box.className); // "card active"
// Attribute name is class (not className)
console.log(box.getAttribute("class")); // "card active"
// Both stay in sync
box.className = "card highlighted";
console.log(box.getAttribute("class")); // "card highlighted"
box.setAttribute("class", "card");
console.log(box.className); // "card"
For working with CSS classes, prefer the classList API over both className and getAttribute("class"). It provides add, remove, toggle, contains, and replace methods that are safer and easier to use:
let box = document.getElementById("box");
box.classList.add("highlighted");
box.classList.remove("active");
box.classList.toggle("visible");
console.log(box.classList.contains("highlighted")); // true
Summary of Synchronization Rules
| Attribute/Property | Attribute → Property | Property → Attribute |
|---|---|---|
id | Yes | Yes |
class / className | Yes | Yes |
href | Yes (but value differs) | Yes |
src | Yes (but value differs) | Yes |
value (input) | Yes | No |
checked (input) | Only initially | No |
selected (option) | Only initially | No |
disabled | Yes | Yes |
style | Partially | Partially |
Non-Standard Attributes Have No Properties
If an HTML attribute is not recognized as a standard attribute for that element, the browser does not create a corresponding DOM property:
<div id="box" something="custom-value" data-role="admin">
Content
</div>
let box = document.getElementById("box");
// Standard attribute → property exists
console.log(box.id); // "box"
// Non-standard attribute → no property
console.log(box.something); // undefined
// But you can still access it with getAttribute
console.log(box.getAttribute("something")); // "custom-value"
This is an important distinction. getAttribute works with any attribute, standard or not. DOM properties only exist for standard attributes of that element type.
Here is a subtle aspect: an attribute might be standard for one element but non-standard for another:
// "type" is standard for <input>
let input = document.createElement("input");
input.setAttribute("type", "checkbox");
console.log(input.type); // "checkbox" (property exists)
// "type" is NOT standard for <div>
let div = document.createElement("div");
div.setAttribute("type", "checkbox");
console.log(div.type); // undefined (no property)
console.log(div.getAttribute("type")); // "checkbox" (attribute still works)
Non-Standard Attributes and dataset (data-* Attributes)
Non-standard attributes are useful for passing custom data from HTML to JavaScript or for marking elements for CSS selectors. However, using arbitrary attribute names is risky because a non-standard attribute you invent today might become a standard attribute in a future HTML version, causing conflicts.
The solution is data-* attributes. They are a standardized way to attach custom data to HTML elements, guaranteed never to conflict with future standards.
The data-* Convention
Any attribute whose name starts with data- is reserved for programmer use. The HTML specification guarantees these will never be used as standard attributes:
<article
id="post-1"
data-author="Alice"
data-category="javascript"
data-published="2024-03-15"
data-word-count="1500"
data-is-featured="true"
>
<h2>Learning JavaScript</h2>
<p>Article content...</p>
</article>
Reading data-* Attributes with getAttribute
You can always use getAttribute to read them:
let article = document.getElementById("post-1");
console.log(article.getAttribute("data-author")); // "Alice"
console.log(article.getAttribute("data-category")); // "javascript"
console.log(article.getAttribute("data-word-count")); // "1500"
console.log(article.getAttribute("data-is-featured")); // "true"
The dataset Property: A Cleaner API
Every element has a dataset property that provides a DOMStringMap of all data-* attributes. The data- prefix is stripped and the rest of the name is converted to camelCase:
let article = document.getElementById("post-1");
// data-author → dataset.author
console.log(article.dataset.author); // "Alice"
// data-category → dataset.category
console.log(article.dataset.category); // "javascript"
// data-published → dataset.published
console.log(article.dataset.published); // "2024-03-15"
// data-word-count → dataset.wordCount (hyphen → camelCase)
console.log(article.dataset.wordCount); // "1500"
// data-is-featured → dataset.isFeatured (hyphens → camelCase)
console.log(article.dataset.isFeatured); // "true"
The naming conversion rule:
- Remove the
data-prefix - Convert each
-xto uppercaseX(kebab-case to camelCase)
| HTML Attribute | dataset Property |
|---|---|
data-name | dataset.name |
data-user-id | dataset.userId |
data-is-active | dataset.isActive |
data-max-retry-count | dataset.maxRetryCount |
Writing data-* Attributes with dataset
The dataset property is read-write. Setting a property on dataset creates or updates the corresponding data-* attribute:
let article = document.getElementById("post-1");
// Set new data attributes
article.dataset.readTime = "5 min";
console.log(article.getAttribute("data-read-time")); // "5 min"
// Update existing data attributes
article.dataset.author = "Bob";
console.log(article.getAttribute("data-author")); // "Bob"
// Delete a data attribute
delete article.dataset.isFeatured;
console.log(article.hasAttribute("data-is-featured")); // false
data-* Values Are Always Strings
Like all HTML attributes, data-* values are strings. You need to parse them if you need other types:
let article = document.getElementById("post-1");
// ❌ These are strings, not their intended types
console.log(article.dataset.wordCount); // "1500" (string)
console.log(article.dataset.isFeatured); // "true" (string)
console.log(typeof article.dataset.wordCount); // "string"
// ✅ Parse to the correct types
let wordCount = parseInt(article.dataset.wordCount, 10); // 1500 (number)
let isFeatured = article.dataset.isFeatured === "true"; // true (boolean)
// For complex data, you can use JSON
let element = document.getElementById("config");
element.dataset.options = JSON.stringify({ theme: "dark", fontSize: 14 });
let options = JSON.parse(element.dataset.options);
console.log(options.theme); // "dark"
Using data-* Attributes in CSS
A major benefit of data-* attributes is that they can be used in CSS selectors:
/* Style elements based on data attributes */
article[data-category="javascript"] {
border-left: 4px solid yellow;
}
article[data-is-featured="true"] {
background-color: #fffde7;
}
/* Use data attributes for content */
article::before {
content: "By " attr(data-author);
font-style: italic;
color: #666;
}
Practical Use Cases for data-* Attributes
Event delegation with action identifiers:
<div id="toolbar">
<button data-action="bold">B</button>
<button data-action="italic">I</button>
<button data-action="underline">U</button>
<button data-action="link">🔗</button>
</div>
document.getElementById("toolbar").addEventListener("click", (event) => {
let button = event.target.closest("[data-action]");
if (!button) return;
let action = button.dataset.action;
console.log(`Performing action: ${action}`);
switch (action) {
case "bold": document.execCommand("bold"); break;
case "italic": document.execCommand("italic"); break;
case "underline": document.execCommand("underline"); break;
case "link": {
let url = prompt("Enter URL:");
if (url) document.execCommand("createLink", false, url);
break;
}
}
});
Configuration stored in HTML:
<div id="carousel"
data-interval="3000"
data-autoplay="true"
data-transition="fade"
data-loop="true">
<img src="slide1.jpg">
<img src="slide2.jpg">
<img src="slide3.jpg">
</div>
let carousel = document.getElementById("carousel");
let config = {
interval: parseInt(carousel.dataset.interval, 10) || 5000,
autoplay: carousel.dataset.autoplay === "true",
transition: carousel.dataset.transition || "slide",
loop: carousel.dataset.loop !== "false"
};
console.log(config);
// { interval: 3000, autoplay: true, transition: "fade", loop: true }
Tracking state on elements:
function toggleFavorite(button) {
let isFavorited = button.dataset.favorited === "true";
button.dataset.favorited = (!isFavorited).toString();
button.textContent = isFavorited ? "☆ Favorite" : "★ Favorited";
// CSS can react to this change:
// button[data-favorited="true"] { color: gold; }
}
Property Types: Strings, Booleans, Objects
While HTML attributes are always strings, DOM properties have proper JavaScript types. This is a crucial difference that affects how you work with them.
String Properties
Most DOM properties are strings, matching their attribute counterparts:
let link = document.querySelector("a");
console.log(typeof link.id); // "string"
console.log(typeof link.className); // "string"
console.log(typeof link.href); // "string"
console.log(typeof link.title); // "string"
Boolean Properties
Some DOM properties are booleans, even though the corresponding HTML attributes are either present or absent (not "true" or "false"):
<input type="checkbox" id="agree" checked disabled>
let checkbox = document.getElementById("agree");
// DOM properties are booleans
console.log(checkbox.checked); // true
console.log(checkbox.disabled); // true
console.log(typeof checkbox.checked); // "boolean"
console.log(typeof checkbox.disabled); // "boolean"
// HTML attributes are strings (or null)
console.log(checkbox.getAttribute("checked")); // "" (empty string, attribute exists)
console.log(checkbox.getAttribute("disabled")); // "" (empty string, attribute exists)
This difference matters enormously when setting values:
let checkbox = document.getElementById("agree");
// ✅ Correct: use the boolean property
checkbox.checked = false;
console.log(checkbox.checked); // false
// ❌ Incorrect: setting the attribute to "false" does NOT uncheck it
checkbox.setAttribute("checked", "false");
console.log(checkbox.checked); // true!
// The attribute "checked" EXISTS (even with value "false"), so the checkbox stays checked
This is one of the most confusing behaviors in the DOM. For boolean HTML attributes, the presence of the attribute means true, regardless of its value. Setting checked="false" in HTML still makes the checkbox checked. The only way to "uncheck" via attributes is to remove the attribute entirely:
// ✅ To uncheck via attributes, REMOVE the attribute
checkbox.removeAttribute("checked");
// ✅ But using the property is much simpler and correct
checkbox.checked = false;
Common boolean attribute/property pairs:
| HTML Attribute | DOM Property | Type |
|---|---|---|
checked | checked | boolean |
disabled | disabled | boolean |
readonly | readOnly | boolean |
required | required | boolean |
hidden | hidden | boolean |
selected | selected | boolean |
multiple | multiple | boolean |
autofocus | autofocus | boolean |
autoplay | autoplay | boolean |
Object Properties
Some DOM properties return objects rather than primitive values:
let div = document.querySelector("div");
// style is a CSSStyleDeclaration object
console.log(typeof div.style); // "object"
console.log(div.style instanceof CSSStyleDeclaration); // true
div.style.color = "red";
div.style.fontSize = "18px";
// getAttribute returns the SERIALIZED string
console.log(div.getAttribute("style")); // "color: red; font-size: 18px;"
// The property is a rich object, the attribute is a flat string
let input = document.querySelector('input[list="options"]');
// The list property returns the actual <datalist> element (an object)
console.log(input.list); // <datalist id="options">...</datalist>
// The attribute returns the string ID
console.log(input.getAttribute("list")); // "options"
Attribute vs. Property Decision Guide
| Situation | Use Property | Use Attribute |
|---|---|---|
| Read/write the current value of an input | input.value | No |
| Read/write a boolean state (checked, disabled) | input.checked = true | No |
| Get the initial/default value from HTML | No | getAttribute("value") |
| Get the raw href as written in HTML | No | getAttribute("href") |
Work with non-standard or data-* attributes | dataset.x | getAttribute("data-x") |
| Read/write standard attributes on known elements | Property (usually) | Either works |
| Work with SVG or XML attributes | No | getAttribute |
As a general rule: use DOM properties for standard attributes on HTML elements. They have correct types (booleans, numbers, objects) and represent the current state. Use getAttribute / setAttribute when you need the raw HTML attribute value, when working with non-standard attributes, or when dealing with SVG/XML elements.
The attributes Collection
Every element has an attributes property that returns a live NamedNodeMap of all its attributes. This collection provides a way to iterate over all attributes on an element.
Reading All Attributes
<a id="link"
href="https://example.com"
target="_blank"
class="nav-link"
data-section="header"
title="Example Site">
Example
</a>
let link = document.getElementById("link");
let attrs = link.attributes;
console.log(attrs.length); // 5
// Iterate over all attributes
for (let attr of attrs) {
console.log(`${attr.name} = "${attr.value}"`);
}
// id = "link"
// href = "https://example.com"
// target = "_blank"
// class = "nav-link"
// data-section = "header"
// title = "Example Site"
Attr Objects
Each item in the attributes collection is an Attr object with name and value properties:
let link = document.getElementById("link");
let hrefAttr = link.attributes[1]; // Second attribute (href)
console.log(hrefAttr.name); // "href"
console.log(hrefAttr.value); // "https://example.com"
// Access by name
let classAttr = link.attributes.getNamedItem("class");
console.log(classAttr.name); // "class"
console.log(classAttr.value); // "nav-link"
// Shorthand: access by name directly
console.log(link.attributes["class"].value); // "nav-link"
Practical Use: Cloning Attributes
The attributes collection is useful when you need to copy all attributes from one element to another:
function copyAttributes(source, target) {
for (let attr of source.attributes) {
target.setAttribute(attr.name, attr.value);
}
}
let original = document.getElementById("link");
let clone = document.createElement("a");
clone.textContent = "Cloned Link";
copyAttributes(original, clone);
console.log(clone.outerHTML);
// <a id="link" href="https://example.com" target="_blank" class="nav-link" ...>Cloned Link</a>
Practical Use: Serializing Element Configuration
function getElementConfig(element) {
let config = {};
for (let attr of element.attributes) {
config[attr.name] = attr.value;
}
return config;
}
let link = document.getElementById("link");
console.log(getElementConfig(link));
// {
// id: "link",
// href: "https://example.com",
// target: "_blank",
// class: "nav-link",
// "data-section": "header",
// title: "Example Site"
// }
Live Nature of attributes
The attributes collection is live. It updates when attributes are added or removed:
let div = document.querySelector("div");
let attrs = div.attributes;
console.log(attrs.length); // e.g., 2
div.setAttribute("data-new", "value");
console.log(attrs.length); // 3 (updated automatically)
div.removeAttribute("data-new");
console.log(attrs.length); // 2 (updated automatically)
Complete Practical Example
Here is a comprehensive example that demonstrates attributes, properties, dataset, and their interactions:
<!DOCTYPE html>
<html>
<head>
<title>Attributes and Properties Demo</title>
<style>
.card { border: 1px solid #ddd; padding: 16px; margin: 8px; border-radius: 8px; }
.card[data-priority="high"] { border-color: red; }
.card[data-priority="medium"] { border-color: orange; }
.card[data-priority="low"] { border-color: green; }
.card[data-completed="true"] { opacity: 0.5; text-decoration: line-through; }
</style>
</head>
<body>
<div id="app">
<h1>Task Board</h1>
<div id="tasks">
<div class="card"
data-task-id="1"
data-priority="high"
data-completed="false"
data-created="2024-03-15">
<h3>Fix login bug</h3>
<label>
<input type="checkbox" class="complete-toggle"> Done
</label>
</div>
<div class="card"
data-task-id="2"
data-priority="medium"
data-completed="false"
data-created="2024-03-16">
<h3>Update documentation</h3>
<label>
<input type="checkbox" class="complete-toggle"> Done
</label>
</div>
<div class="card"
data-task-id="3"
data-priority="low"
data-completed="false"
data-created="2024-03-17">
<h3>Refactor utils module</h3>
<label>
<input type="checkbox" class="complete-toggle"> Done
</label>
</div>
</div>
<div id="info"></div>
</div>
<script>
let tasksContainer = document.getElementById("tasks");
// Event delegation using data attributes
tasksContainer.addEventListener("change", (event) => {
if (!event.target.matches(".complete-toggle")) return;
let card = event.target.closest(".card");
// Use the boolean PROPERTY to get the current checkbox state
let isCompleted = event.target.checked; // boolean (not string!)
// Use dataset to update the data attribute
card.dataset.completed = isCompleted.toString();
// CSS automatically updates (opacity, line-through)
// because the data-completed attribute changed
logTaskInfo(card);
});
function logTaskInfo(card) {
let checkbox = card.querySelector(".complete-toggle");
console.log("=== Task Info ===");
// dataset properties (strings from data-* attributes)
console.log("Task ID:", card.dataset.taskId); // "1" (string)
console.log("Priority:", card.dataset.priority); // "high" (string)
console.log("Completed:", card.dataset.completed); // "true" (string!)
console.log("Created:", card.dataset.created); // "2024-03-15" (string)
// DOM property (proper boolean)
console.log("Checkbox checked:", checkbox.checked); // true (boolean!)
// Attribute vs Property difference
console.log("Attribute 'checked':", checkbox.getAttribute("checked")); // null (never set in HTML)
console.log("Property checked:", checkbox.checked); // true
// All attributes on the card
console.log("All attributes:");
for (let attr of card.attributes) {
console.log(` ${attr.name} = "${attr.value}"`);
}
}
// Demonstrate attribute vs property for href
function showAttributePropertyDifference() {
let link = document.createElement("a");
link.setAttribute("href", "/about");
document.body.appendChild(link);
let info = document.getElementById("info");
info.innerHTML = `
`;
link.remove();
}
showAttributePropertyDifference();
</script>
</body>
</html>
This example demonstrates:
- Boolean properties (
checkbox.checked) versus string attributes (getAttribute("checked")) datasetfor reading and writingdata-*attributes with camelCase conversion- CSS responding to
data-*attribute changes without any class toggling - Event delegation using
closestandmatcheswith data attributes - The attribute vs. property difference for
href
Summary
HTML attributes and DOM properties are two related but distinct systems for working with element data.
HTML Attributes:
- Written in HTML markup
- Accessed via
getAttribute,setAttribute,removeAttribute,hasAttribute - Names are case-insensitive
- Values are always strings
- Represent the initial state from the HTML source
- Work with any attribute (standard and non-standard)
- The
attributescollection lists all attributes on an element
DOM Properties:
- JavaScript object properties on DOM nodes
- Accessed via dot notation (
element.id,element.value) - Names are case-sensitive
- Values have proper types (strings, booleans, objects, numbers)
- Represent the current state of the element
- Only exist for standard attributes of that element type
Synchronization:
- Most standard attributes sync bidirectionally with their properties
value,checked, andselectedsync only from attribute to property (one-way)hrefandsrcproperties return resolved URLs, while attributes return raw values- Non-standard attributes have no corresponding properties
data-* Attributes and dataset:
data-*attributes are the standard way to attach custom data to elements- Access via
element.dataset.camelCaseName - Hyphens in attribute names convert to camelCase in
dataset - Values are always strings (parse manually for other types)
- Usable in CSS selectors for styling
- Guaranteed to never conflict with future HTML standards
Key Rules:
- Use properties for standard attributes (correct types, current state)
- Use
getAttributefor raw attribute values, non-standard attributes, or SVG - Use
datasetfor custom data instead of inventing non-standard attributes - For boolean attributes (checked, disabled), always use the property, never
setAttribute("checked", "false")