Attributes, classes and inline styles from script
Attribute versus property, boolean attributes, the dataset naming rules, classList operations, and the difference between inline style and computed style.
Attributes and their properties
const input = document.querySelector('#email');
// attributes: always strings, always what the markup said
input.getAttribute('type'); // "email"
input.setAttribute('maxlength', '40');
input.hasAttribute('required'); // boolean
input.removeAttribute('maxlength');
input.toggleAttribute('required');
// properties: the live, typed value the DOM is actually using
input.type; // "email"
input.required; // true/false
input.maxLength; // number
const link = document.querySelector('a');
link.getAttribute('href'); // "/docs" - as written
link.href; // "https://site.test/docs" - resolved- For
value,checked,selectedandhref, attribute and property can disagree. The property is what the user sees; the attribute is what the markup said. - After a user types into an input,
getAttribute("value")still returns the original. Useinput.value. - Setting the
valueattribute changes the input's default; setting the property changes the current value. el.attributesis a live NamedNodeMap, useful for copying every attribute onto another element.
// boolean attributes: presence is the value, not the string
const btn = document.querySelector('button');
btn.setAttribute('disabled', 'false'); // still disabled - presence is enough
btn.disabled = false; // this is what you meant
btn.removeAttribute('disabled'); // the other correct form
// copy every attribute from one node to another
for (const { name, value } of [...source.attributes]) {
target.setAttribute(name, value);
}⚠️
Writing
setAttribute("disabled", "false") disables the element. Boolean attributes are true when present regardless of their value, so the only way to turn one off is to remove it - or set the matching property to false.Dataset and classList
<div id="row"
data-user-id="42"
data-role="admin"
data-gdpr-consent="2026-01-04"></div>const row = document.querySelector('#row');
row.dataset.userId; // "42" data-user-id
row.dataset.role; // "admin"
row.dataset.gdprConsent; // "2026-01-04"
row.dataset.userId = '43'; // writes back to the attribute
delete row.dataset.role; // removes data-role
row.getAttribute('data-user-id'); // still works, same storage- Dashes become camelCase:
data-user-idisdataset.userId. A name that is not valid in a property path has to be read withgetAttribute. - Everything is a string.
dataset.countis"3", not3. - Prefer data attributes for state that belongs to the markup and must be readable in the DOM inspector; prefer a JS variable for state the user never needs to see.
const el = document.querySelector('.card');
el.classList.add('is-open');
el.classList.remove('is-open');
el.classList.toggle('is-open'); // add or remove, returns the result
el.classList.toggle('is-open', isOpen); // explicit, idempotent
el.classList.replace('is-open', 'is-closed');
el.classList.contains('is-open'); // boolean
[...el.classList]; // array of class names
el.className = 'card is-open'; // replaces the whole list at onceclassList.toggle(name, force) is the version to use in rendering code: given the same state twice it produces the same result, which matters when the same render function runs more than once.
Inline style versus computed style
const box = document.querySelector('.box');
// inline style: only what was set on the element itself
box.style.backgroundColor = '#0af'; // camelCase property names
box.style.setProperty('--brand', '#0af');
box.style.setProperty('--gap', '12px', 'important');
box.style.removeProperty('--brand');
console.log(box.style.width); // "" unless set inline
console.log(box.getAttribute('style')); // the whole inline string
// computed style: the value after the cascade, always resolved
const cs = getComputedStyle(box);
cs.backgroundColor; // "rgb(0, 170, 255)"
cs.width; // "320px" - even if set as 20rem
cs.getPropertyValue('--brand').trim(); // custom properties resolve here
cs.marginTop; // "0px" even with no rule
// a temporary override without touching the stylesheet
const prev = box.style.display;
box.style.display = 'none';| Read with | Returns | Use for |
|---|---|---|
el.style.prop | Only the inline value | Detecting what you set yourself |
getComputedStyle(el).prop | Resolved used value | Layout, colours, custom properties |
el.getAttribute("style") | Raw attribute string | Copying inline style verbatim |
el.style.setProperty() | Sets one declaration | Custom properties and important |
// reading a custom property set by a stylesheet
const brand = getComputedStyle(document.documentElement)
.getPropertyValue('--brand').trim();
// then use it in JS that cannot be expressed in CSS
drawChart(brand);FAQ
Should I set styles with classList or with el.style?
Use classes for anything that is a state or a design decision - they are reusable, reviewable in CSS, and can be overridden by a media query. Use inline style for values that genuinely come from data at runtime, such as a width from a measurement or a colour from a user's preference.
Why is getComputedStyle returning an empty string?
You asked for a property the element does not have, or you used the wrong property name. Computed style uses the CSS names for custom properties but camelCase for standard ones, and an unknown property returns an empty string rather than throwing.
Related
Reading and updating content safely Events and delegation in depth
Last refreshed 2026-09-18.