Properties vs Attributes
Attributes live in HTML and are always strings
Section titled “Attributes live in HTML and are always strings”When you write <my-card count="5" label="hello"> in HTML, the browser stores those as attribute nodes — and every attribute value is a string, no matter what you type. Calling element.getAttribute('count') returns the string "5", not the number 5.
const el = document.querySelector('my-card');console.log(el.getAttribute('count')); // "5" (string)console.log(el.getAttribute('label')); // "hello" (string)console.log(el.getAttribute('missing')); // null (not present)This is important: getAttribute returns either a string or null — never a boolean, number, array, or object.
Properties are JavaScript values
Section titled “Properties are JavaScript values”Properties are regular JavaScript object properties on the element instance. They can be any JS type: booleans, numbers, arrays, objects, or functions. You access them with dot notation, not getAttribute.
el.count = 5; // numberel.rows = [{ id: 1 }]; // arrayel.config = { theme: 'dark', size: 'lg' }; // objectel.disabled = true; // booleanThese property values are not serialised back into the DOM unless your component explicitly does so. A property set on an element instance lives only in JavaScript memory.
Boolean attributes: check presence, not value
Section titled “Boolean attributes: check presence, not value”HTML boolean attributes work by presence or absence — if the attribute exists on the element, it is true; if it is absent, it is false. The value of the attribute string does not matter.
That means <my-btn disabled=""> and <my-btn disabled="false"> are both disabled because the attribute is present. Only <my-btn> (no attribute at all) means not disabled.
Never compare getAttribute('disabled') === 'true' or === true. Always use hasAttribute or check the return value against null.
// Boolean attribute — check presence, not valueclass MyButton extends HTMLElement { get disabled() { return this.hasAttribute('disabled'); } set disabled(v) { v ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); }}Notice the setter sets the attribute to the empty string '' when truthy — that is the idiomatic HTML pattern. The getter ignores whatever string is stored; it just asks: is the attribute there at all?
Rich data: use a property, not an attribute
Section titled “Rich data: use a property, not an attribute”When you need to pass an array, an object, or any non-string value into a component, a JS property setter is the right tool. Trying to serialize objects into attribute strings (e.g. data="[object Object]") is fragile and lossy.
// Rich data — use a property, not an attributeclass DataTable extends HTMLElement { set rows(data) { // data is an array of objects this._rows = data; this._render(); } get rows() { return this._rows || []; } _render() { /* ... */ }}Then from JavaScript:
const table = document.querySelector('data-table');table.rows = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' },];The setter receives the actual array — no parsing required. When the data changes, just assign the property again and _render runs.
Live demo
Section titled “Live demo”The <user-card> element below combines both techniques:
- A boolean attribute
premium— when present it renders a crown - A property setter
profile— accepts an object{ name, role }and re-renders
The HTML sets the premium attribute directly in markup. The JavaScript then assigns el.profile = { name: 'Alice', role: 'Admin' } — a plain object — triggering the property setter and re-rendering the card.
Summary: which to use?
Section titled “Summary: which to use?”| Scenario | Use |
|---|---|
| Simple string config (label, type, variant) | Attribute |
| Boolean flag (disabled, hidden, selected) | Boolean attribute + hasAttribute |
| Number, object, array, or function | Property setter |
| Value set from HTML markup only | Attribute |
| Value set from JavaScript | Either — prefer property for non-strings |