CSS (and JavaScript) attribute selectors match elements by attribute-presence or attribute-value, not property-presence or value.
Kyle, of You Don't Know JS fame, tweeted this complaint: https://twitter.com/getify/status/1147165724819034116
The fact that the CSS selector
input[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 supporttype=date
(safari)?
But doing that would have poured gasoline on a smoldering opinion fire fight with Kyle.
What's confusing to JavaScript-first practitioners is a combination of results given today's habits and implementations.
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).
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.
- neither
input[type=text]
nordocument.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)
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">
In other words, CSS (and JavaScript) attribute selection really is attribute-based, not property-based.
Until you understand that, You Don't Know CSSTM.
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.