Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active June 22, 2020 22:30
Show Gist options
  • Save dfkaye/cc68840cfa1f9e209045552c5ff15374 to your computer and use it in GitHub Desktop.
Save dfkaye/cc68840cfa1f9e209045552c5ff15374 to your computer and use it in GitHub Desktop.
You Don't Know CSS - attribute selectors are not property selectors.

You Don't Know CSSTM

CSS (and JavaScript) attribute selectors match elements by attribute-presence or attribute-value, not property-presence or value.

Background

Kyle, of You Don't Know JS fame, tweeted this complaint: https://twitter.com/getify/status/1147165724819034116

The fact that the CSS selectorinput[type=text] doesn't match <input> is aggravating. I always forget <input type=text>.

IOW, I wish attribute selectors matched against default attribute values.

I replied https://twitter.com/dfkaye/status/1147186340531851266

Not that you asked me, but I like that CSS works that way - so we can use the :not() selector to find/fix the unexpected, like so:

input:not([type]) { outline: 5px dotted red; }

He replied: https://twitter.com/getify/status/1147189669966352384

that's a pretty small use-case compared to how often you want affirmative matching of <input> elements.

I thought about replying with questions like

  • Should a[href=""] find anchors without an href because default href is ""?
  • Should input[value=""] find inputs without value attributes?
  • Should [type=text] find input [type=date] elements in browsers that don't support type=date (safari)?

But doing that would have poured gasoline on a smoldering opinion fire fight with Kyle.

Should be a blog post...

What's confusing to JavaScript-first practitioners is a combination of results given today's habits and implementations.

HTML5 Permissiveness

On input tags, the type attribute is not required. A missing type attribute defaults behavior to type=text but not the default selectability in CSSOM (if such there be).

Tag Attributes are not Element Properties

When the browser parses HTML

  • default properties (not attributes) are set on DOM objects in the context of the imperative language, JavaScript
  • default attributes are not updated by the browser on the HTML, whether the original or the generated source.

Selectors in CSS and JavaScript match on the DOM created from the initial markup...

  • neither input[type=text] nor document.querySelectorAll('input[type=text]') will find <input> (no type attribute)
  • whereas, document.querySelector('input:not([type])').type; returns "text" (instead of null or undefined)
  • and, document.querySelector('input:not([pattern])').pattern; returns "" (instead of null or undefined)

...unless the DOM has been updated imperatively by JavaScript:

Assume <input value="nothing"> in the HTML.

var input = document.querySelector('input:not([type])');
var nothing = document.querySelector('[value=nothing]');

These selectors match the same element

console.log(input === nothing); // selectors match input without type, value equal to "nothing"

Accessing the type property returns the default value, not the attribute value:

console.log(input.type); // "text" <- because this is not a node attribute, it is an element property

Accessing the node attribute returns the current attribute value:

console.log(input.getAttribute('type'); // null <- because the node attribute does not exist

When we update a node attribute directly with an invalid value, the DOM property will still use the default property value...

input.setAttribute('type', 'empty');
console.log(input.type); // 'text' <- because the attribute value is not supported, the DOM returns the default property

But the CSS and JavaScript selectors will find the element based on this new attribute value:

document.querySelector('[type=empty]'); // <input value="nothing" type="empty">

On the other hand, when we update (mutate, modify, change, assign) a DOM element property, a shorthand heuristic looks for a matching node attribute name, and modifies that if it exists.

input.type = 'nothing';
document.querySelectorAll('input[type=nothing]')`; // <input type="nothing" value="nothing">

Summing up

In other words, CSS (and JavaScript) attribute selection really is attribute-based, not property-based.

Until you understand that, You Don't Know CSSTM.

Addendum: I don't know CSS

This happened on June 22, 2020.

I asserted these were different:

tag[attr] selects by presence of attr.
tag[attr=""] selects by exact attr value.

But I was corrected by Mattia Astorino.

Made a codepen to test it out, and by golly, he's right!

Add my name (alas) to the (ever-growing) list of people who don't know CSS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment