HTML Attributes, Properties, and Values

EisenbergEffect
7 min readOct 31, 2023

HTML is quite different from other declarative systems used to build UI today. As a result, those of us who have worked on other platforms often bring an incorrect set of assumptions with us when approaching the web. To further confuse matters, some popular JavaScript frameworks abstract the platform in ways that fundamentally miseducate those new to the field.

To address this, I’ve been trying to provide some content around web fundamentals. I’d like to continue that in this blog post by talking about something that I personally misunderstood for a long time: HTML Attributes. But not just HTML attributes. We also need to talk about properties, input values, and how all three relate.

Overview

Everything in an HTML document inherits from the base Node type with the most common type of node being an instance of HTMLElement. There are a wide variety of HTML element types: h1, p, div, img, input, etc. Each element can also have attributes. For example, img tags have a src attribute, while input tags have disabled and value attributes. But not all these attributes work the same way. In fact, src, disabled, and value are all quite different. Let’s explore their differences as well as some common terminology you’ll find in related W3C specifications.

IMPORTANT: If you want to learn more about Web Components as well as dozens of related Web Standards such as the ones discussed in this blog post, check out my upcoming “Web Component Engineering” course. Between now and the launch on Nov. 7, I’m running a presale. Use code EARLYBIRD at checkout to save $100 on this 13 module, 170+ video course with a fully interactive learning environment!

Content Attributes

A content attribute is an attribute that is set directly in HTML. It is always a string and can be retrieved through HTMLElement#getAttribute() and set through HTMLElement#setAttribute(). For example, the id and src attributes on an img element are content attributes:

<img id="logoImg" src="./image/logo.svg">

They can be interacted with in JavaScript as follows:

const logoImg = document.getElementById("logoImg");

// Get the src content attribute.
const src = logoImg.getAttribute("src");

// Set the src content attribute.
logoImg.setAttribute("src", "./image/logo-black.svg");

Boolean Attributes

A boolean attribute is a special type of content attribute whose value is determined by its presence or absence. For example, disabled is considered true when it is present on an element, and false otherwise. Here’s a disabled input element:

<input id="nameInput" type="text" disabled>

It’s important to remember that you don’t set a boolean attribute equal to “true” or “false”. Instead, you add or remove the attribute.

const nameInput = document.getElementById("nameInput");

// Enable the input by removing the disabled attribute.
nameInput.removeAttribute("disabled");

// Disable the input by force adding the disabled attribute.
nameInput.toggleAttribute("disabled", true);

// Enable the attribute by toggling it from the previous state.
nameInput.toggleAttribute("disabled");

Event Handler Attributes

In legacy HTML, you may also see event handler content attributes that look like this:

<button type="button" onclick="console.log('Hello World!')">Greet</button>

These on* attributes accept a string that will get automatically converted into the body of a function. DO NOT USE event handler attributes. This introduces a gaping security whole into your application. Instead, add event handlers with the addEventListener() API.

IDL Attributes

IDL stands for “Interface Definition Language” and WebIDL is the variant of IDL that is used to describe the JavaScript side of the DOM, as well as other browser APIs. The term “IDL Attribute” can be a bit confusing to those new to “standards speak” because we’re not talking about the same types of attributes as above. An IDL Attribute manifests itself as a JavaScript property.

The HTML spec defines a number of JavaScript properties (IDL Attributes) that map to content, boolean, and event handler attributes. Typically, their names are the same. For example, going back to our img example from above, we could read and write the src through a property instead of using the attribute API:

const logoImg = document.getElementById("logoImg");

// Get the src via the IDL Attribute/JS Property
const src = logoImg.src;

// Set the src via the IDL Attribute/JS Property
logoImg.src = "./image/logo-black.svg";

IMPORTANT: Setting an on* JavaScript property to a string is just as dangerous as setting the attribute in HTML. Don’t do it.

Some properties don’t have the same name as their attributes. This is typically for casing reasons. i.e. JavaScript property names are case sensitive while HTML attribute names are not. This is most apparent with ARIA globals. Here’s an example in HTML:

<span id="checkBoxInput"
role="checkbox"
aria-checked="false"
tabindex="0"></span>

And here’s how we access aria-checked via its IDL Attribute:

const checkBoxInput = document.getElementById("checkBoxInput");
checkBoxInput.ariaChecked = true; // Notice the different casing.

NOTE: aria-checked is not a boolean attribute! It takes the strings “true” or “false” in the attribute and the property gets/sets a boolean. This is common in ARIA. As a reminder, boolean attributes are true if they are present and false if they are absent. Their properties will return true/false based on this. If you give a boolean attribute a “false” value, then it will be true because the string “false” is truthy.

There’s another scenario where the names are completely different as well. This is when the content attribute’s name would have been invalid as a JavaScript property. Here’s a bit of HTML that sets up the problem:

<label id="myLabel" for="checkBoxInput" class="fancy-label">…</label>

When the DOM API was originally designed, neither for nor class could be used as property names in JavaScript, because they are reserved words. While that restriction was lifted for members in a later version of JavaScript, the original DOM APIs remain. Here’s how we access them through code:

const myLabel = document.getElementById("myLabel");
myLabel.htmlFor = "theIdOfSomeElement";
myLabel.className = "some other classes";

There’s at least one other detail about IDL attributes that’s pretty important: some of them reflect and some do not.

What do I mean by “reflect”?

Consider again that we have two ways of reading/writing DOM state. We can use the getAttribute() / setAttribute() API or we can use the matching JavaScript property (IDL Attribute). However, not all JavaScript properties update the matching content attribute value. When changes to the property update the attribute, it is said to be a “reflecting IDL attribute” and when changes made through the property do not update the attribute, it is said to be a “non-reflecting IDL attribute”.

What’s the most common example of this? Well, that would be our friend the input’s value property.

Input value

The input element has a value content attribute and a value property (IDL Attribute), but the value property does not reflect its changes back to the value content attribute.

Why doesn’t an input's value property write back to its value content attribute?

There may be multiple reasons for this, but one reason is certainly to support form resets. When a form reset occurs, the browser needs a piece of state to tell it what the input should be reset to. It uses the value content attribute for this. If the value property were to update the attribute, it would erase the data needed to enable form reset.

So, in the case of input elements, it’s best to think of the value content attribute as the “initial” or “default” value of the input, while thinking of the value property (IDL Attribute) as the “current” value of the input.

Additionally, there is a defaultValue property (IDL Attribute) which does reflect its changes…back to the value content attribute.

Here’s a summary:

  • The value content attribute represents the default/initial value of an input.
  • The value property (IDL Attribute) represents the current value of an input. It does not reflect back to the value content attribute or affect the defaultValue property.
  • The defaultValue property (IDL Attribute) does the same thing as the value content attribute, which represents the default/initial value of the input. When you change this property, it does reflect back to the value content attribute, but does not affect the value property (IDL Attribute).

Special Thanks

Special thanks for this blog post goes out to jods16, dfkaye, and redfeet for teaching me about the little documented defaultValue property, which led me to track down its relationship to the value attribute. Even though I’ve worked with HTML for many years, and written libraries and frameworks, I was quite unaware of this property. We can always learn new things.

ASIDE: When I first encountered defaultValue, it was in JSX. At the time, I thought it was some strange React-ism since there is no such content attribute as defaultValue. For those with React experience, given the information in this post, can you piece together why React uses defaultValue in JSX rather than value as HTML does?

Wrapping Up

HTML attributes, properties, and values are some of the most fundamental characteristics of HTML, but also some of the most misunderstood. So much so, that more than one JavaScript framework over the years has messed this up, leading to various problems. With a solid understanding of these HTML fundamentals, we are better equipped to use the platform and create longer-lived, more interoperable solutions.

If you enjoyed this look into Web Standards, you might want to check out my Web Component Engineering course. I’d also love it if you would subscribe to this blog, subscribe to my YouTube channel, or follow me on twitter. Your support greatly helps me continue writing and bringing this kind of content to the broader community. Thank you!

--

--

EisenbergEffect

W3C, WICG & TC39 contributor focused on Web Standards, UI Architecture & Engineering Culture. Former Technical Fellow, Principal Architect & VP-level tech lead.