HTML Attributes, Properties, and Values
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 thevalue
content attribute or affect thedefaultValue
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 thevalue
content attribute, but does not affect thevalue
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 asdefaultValue
. For those with React experience, given the information in this post, can you piece together why React usesdefaultValue
in JSX rather thanvalue
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!