Element Attributes Are Not Element Properties
Published 2025-04-30, About 6 minute read.
Something that has come up a number of times as people are stepping away from
their javascript frameworks and leaping into web components is that they don't
know the difference between attributes and properties. This matters because
there's a big difference between the two, and you need to know when
to use either or both with your custom elements.
Let's jump right in.
Element Attributes 📎
An attribute are those things in html that allow us to attach key-value
information to elements:
These are in HTML, XML, and XHTML. In all those cases, the values are only
ever strings.
The one exception to this is in HTML where you can have an attribute without a
value, in which the value is just an empty string "" if you were
to retrieve it in javascript. These are generally considered for boolean
states like disabled or hidden.
hi
hi
Element Properties 📎
Properties have a special meaning for Elements. When you inspect the DOM in
the browser, and inspect a particular node, you have an
Element in hand. That Element is an object (in
particular, an Element instance object) and can have its own properties.
Take a look- if you select that custom element in the DOM, it will have that
property. You may not see it right away, because if you log the node or use
$0 in Chrome it will not print the object, but the node
representation:
So, properties are plain ol' javascript properties, just in this case we more
concisely call this a field becuase it's defined on the class
explicitly.
Why the confusion? 📎
I have opinions about how we came to this mess where people confuse the two. I
had to spend a while diving into attributes and properties while first
learning Lit. I'm convinced it has to do with how frameworks use templates.
Take React. They famously switched over to JSX to include templating inside
the actual javascript files. As best I can tell and remember
this happened around 2013-2014.
Despite React saying explicitly in its introductory explanation of JSX that
JSX is not HTML, most people took for granted how important this distinction
was, and continue to do so. JSX is an abstraction that represents components
and elements using XML-like syntax. Because you can freely call components or
regular web elements in the XML you write, this produced a whole generation of
developers that didn't need to distinguish between the abstraction and the
platform API for DOM.
I'm pretty certain that as people got used to seeing this:
They forgot completely about the JSX pragma and desugaring that was happening
under the hood:
JSX is a shorthand for creating a representation of a DOM tree. It is
not HTML or a DOM tree itself.
The important thing here is that attribute above is actually a
property and not an attribute, even though it looks like an attribtue
in JSX. And what confuses things further is that that property could be a
valid attribute eventually when the element is rendered by React if
React recognizes that property is actually a valid HTML attribute.
This is not specific to React, though, because in every other javascript
framework that has declarative templates you have the same issue. The template
is a representation of the DOM tree you want the framework to eventually
render, and so bindings could be either: a property at
runtime that never translates into an attribute in the DOM
or a property that is recognized as a valid HTML attribute
and eventually placed in the DOM as such.
So why are they conflated in Lit? 📎
I think this is easily seen in how decorators are used to automatically set up
attribute bindings in the background. In Lit, you can add a decorator, and
magically the field you're defining on this class has an attribute
automatically set up for that particular property on the custom element
Under the hood, Lit makes this.myProperty reactive to changes if
set directly. It also deduces a corresponding attribute (in this case
myproperty because it doesn't try to guess what the kebab casing
would be) and sets up reactivity so that if the attribute changes in the DOM,
the property changes as well.
This is a great convenience for users of Lit. But it also can mislead
people to think that there is an inherent binding between properties on a
class and the attributes in the DOM. Of course there isn't a binding like this
automatically. Lit just does it for you.
To be fair, Lit also is very explicit about the distinction between attributes
and properties. In
lit-html template bindings
you have to specify when you're passing a property binding or an attribue:
But again this is not HTML! This is the template language
abstraction for elements in
lit-html In
HTML you must use attributes with plain strings or use good 'ol scripts:
If you want the definitive guide of HTML attributes vs DOM
properties, check
this article by Jake Archibald
out. It is exhaustive, and an excellent dive into details
My Law for Learning 📎
I have an aphorism of sorts I like to live by, and I think it's sound advice
for anyone learning and developing their craft in coding:
This is not "grumpy ol' neckbeard who's upset that kids these days can't do
vanilla javascript" advice. This is "I've learned over time that this helps me
be a better developer" advice. Here's what working with vanilla javascript can
unlock if you work in a framework:
-
Poking around at the internals of the framework set you up to better
contribute to open source
-
Understanding what's happening under the hood allows you to know almost
immediately what can and cannot be the source of bugs you encounter
-
Understanding internals frees you to take advantage of escape hatches built
into frameworks (and when to actually use them!)
-
Understanding internals allows you to know when the problem is framework
problem or an inherent problem in general
- Poking at internals or re-creating framework things is fun
-
Internals teach you what's actually available in browser APIs or in
Javascript already
-
Understanding low level docs (like the
ECMAscript spec or internal specs on
MDN) is an extremely transferrable skill and allows you to transfer to other
frameworks and languages more easily
-
You are able to optimize only the things you understand, and so you are
better able to optimize operations in a framework
Find me on Bluesky or Mastodon. I also have an RSS feed here