When <button> isn’t the best choice (it’s slow to create and render and problematic to style)
The <button> element is considerably slower to create in JavaScript, slower to render by the browser and notoriously difficult to style consistently cross browser. Despite being the ‘right’ choice for button duties, it’s not necessarily the ‘best’ choice.
Picture the scene. I’m trying to convince a principal software engineer to use more semantic elements in markup rather than purely generic div
and span
elements. Semantic elements like section
, header
, footer
, and nav
. The conversation went something like this. “We’ll need to test they aren’t any slower to create with JavaScript”, he points out. “Sure, no problem”, I reply, brimming with confidence. “And buttons”, he added, “You’ll need to test buttons, as we’d need to use them all over the place”.
I knew it wasn’t going to end well for the button right then. You see, button
is a bit different. It’s like radio boxes and fieldsets: it does ‘things’.
Do you have a few minutes? I’m going to tell you why arguing the case for using the button
element can be a losing wicket for complex web projects.
Testing the performance of buttons via createElement in JavaScript
First of all, I needed data to ensure that creating semantic elements (section
, nav
, a
, button
etc) in JavaScript was no slower than creating a div
or a span
.
I don’t write smart tests. I’m not clever enough. I certainly wouldn’t get through a FizzBuzz test. So I created something simplistic but hopefully illustrative instead. I made an almost identical page for each of the elements in question. Apart from an h1
tag for a title, there was no markup. Instead, a snippet of JavaScript at the bottom of each page created 200,000 of the element in question and appended them to the body of the page. For each page, that function looks like this:
;(function TimeThisMother() {
window.onload = function(){
setTimeout(function(){
var t = performance.timing;
for (var index = 0; index < 200000; index++) {
var makeDiv = document.createElement("button");
makeDiv.classList.add('a-button');
makeDiv.textContent = 'this is button number ' + index;
document.body.appendChild(makeDiv);
}
console.log("JS took: " + ( (t.loadEventEnd - t.responseEnd) / 1000) + " seconds to create the elements");
}, 0);
};
})();
This is clearly a ridiculous number of elements to add to a page. However, it enables us to clearly see (you’ll be able to see the difference with your own eyes) the performance difference between these elements.
Timing the creation of elements with JavaScript
I also used navigation timing to measure how long it took the JavaScript to create the elements. That value is logged to the console in your dev tools. However, I don’t think that tells us a lot (other than the fact that Safari is BLAZINGLY fast compared to Chrome/Firefox). What’s more illustrative is that after the elements are created, they need to be rendered to the page. This is where the real gaps open up between button
and, well, everything else.
The createElement tests for each element
Click any of the following links, and as you do, start a stopwatch. You’ll want to stop it when you see content appear beneath the h1
tag. Repeat this test and I expect you will get the same result as me: button
is slow. Almost always twice as slow than any other element. In Chrome on my local machine any of the structural elements (div
, section
, nav
etc) takes around 7 seconds. A button
takes around 14 seconds.
Alternatively, if you would like to view some results I ran through Web Page Test, you can view a comparison of the div and button here:
div
renders at 9.6 seconds (median over 3 runs)button
renders at 15.38 seconds (median over 3 runs)
Whhhattttt?????? Go on – do it. Do it now:
- http://benfrain.com/css-performance-tests/createElements/a-test.html
- http://benfrain.com/css-performance-tests/createElements/div-test.html
- http://benfrain.com/css-performance-tests/createElements/section-test.html
- http://benfrain.com/css-performance-tests/createElements/nav-test.html
- http://benfrain.com/css-performance-tests/createElements/button-test.html
If you’d rather peruse them on GitHub they are here: https://github.com/benfrain/css-performance-tests
button, WTF? Is it your styles?
My initial reaction was that it must be due to styles. In any browser, the button
gets so much visual baggage from the User Agent, it must surely add to the render time? In an effort to mitigate the User Agent style sheet in (I was testing mainly in Chrome at this point) I added all the following overrides:
button.a-button {
height: 30px;
width: 100%;
display: block;
font: inherit;
text-align: initial;
padding: 0;
box-sizing: content-box;
border: 0;
outline: 0;
background-color: wheat;
border-bottom: 1px solid #f90;
text-transform: inherit;
text-rendering: inherit;
letter-spacing: inherit;
text-indent: inherit;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
All that, and still I can’t figure out how to get the text starting vertically at the same place as the other elements (just to normalise the layout between the tested elements)!
By the way, in case you missed it, appearance: none;
is back on the standards track. It’s defined in the CSS Basic User Interface Module Level 4. However, it doesn’t currently, and probably won’t ever give us full visual control over buttons.
This is the double-edged sword of User Agent style sheets for the button
element. All UAs refuse to let certain elements controlled entirely with CSS. However, here’s the duality of the situation that creates:
The user agent wants a button
to always look like a button
so that people can always see it is a button
(accessibility is good, M’kay).
Authors want button
s to look how they want, so they don’t look like an obviously UA styled button
(aesthetics are important, M’kay).
button
be a flex container either (and it’s probably correct according to the specs).We can dance around this all day but I venture it’s the primary reason people don’t use button
elements; they look awful and casual CSSers don’t know how to make them behave.
I was initially convinced that the inherent styles were single biggest contributing factor to the speed issue but when I reached out to the Chrome Dev team folks they added further possibilities:
@benfrain @paul_irish Can I see the test? I can imagine the shadow DOM for a button is way more complex than a plain div, so yah.
— Paul Lewis (@aerotwist) May 13, 2015
Talking about button
in relation to Flex, Daniel Holbert, also had some interesting insight in this comment on bugzilla
<button>
is not implementable (by browsers) in pure CSS, so they are a bit of a black box, from the perspective of CSS. This means that they don’t necessarily react in the same way that e.g. a<div>
would.
Hmmm. Mo info, mo problems: I want to use a button
where is makes sense in terms of the DOM, semantically. However, I don’t want the default styles for states. I want to add them myself. I’m guessing this is an impasse?
At this point, I imagine hippy semantic purists shaking their disapproving heads, stroking their beard (including the women) and sipping their ethically sourced Mocha-chocca-latte-chino. I know what they are thinking: I’ve been beaten so easily, I’ve capitulated with ease to the div-soup army.
I don’t know what to tell you. I don’t feel great about it but the truth is that the button
, in this scenario, is just a whole load of pain.
Here’s an analogy. It makes me feel like going out for drinks with my brother. Towards the end of the night he’s drunk too much, started acting up and gets us both into a brawl. I back him up because he’s my brother (girls, I’m assuming you’d have more sense) but privately I’m acknowledging he’s been a complete dick. Next time, when my brother asks if I want to go out on the town, I’ll decline.
But I digress.
Summary
Look, I accept that button
does things, by default, that other elements don’t. It has states, default styling, and most importantly semantic (accessible) meaning. For the vast majority of ‘buttony’ use cases, the button
element is the ‘right thing to use’.
But the specification and user agents make no provision for when we want the semantics of the element without the baggage. I’m sure there’s a reason we can’t but that doesn’t solve the problem at hand.
We’ve all read articles telling us that we should use the button
element for things that are buttons (as opposed to links or otherwise). I totally agree with their sentiment; that is my default position. And realistically, you won’t want 200,000 button
elements on your page (unless you’re designing the full page inventory for ‘Buttons r us’) so this is a none issue right?
No. It’s a real issue. It’s entirely possible that businesses/products that need to decide between using the ‘right’ element, against using the fastest element, are going to opt for the fastest. When every millisecond adds up, conceding speed on an element can be the start of ‘death by a thousand cuts’.
I’m in no way advocating giving up on button
. If you can use it, you should. Instead I’m hoping that in the future there will be a way to have the button
element from a semantic perspective, without the UA baggage and speed penalty. The best we can probably achieve in this scenario currently is ensuring that at least role="button"
is on the element in question for screen readers.
Unless the W3C provides something akin to button-style: none;
, there will be times when authors continue to choose a different element. The difficulty in amending a button
visually and the comparative performance difference makes it more difficult to wholeheartedly argue, “We should be using a button for this”.
“All UAs refuse to let certain elements controlled entirely with CSS…”
Well that was technically supposed to be possible with style tags within the Shadow DOM of such elements & shadow-piercing CSS combinators, but the latter’s no longer an option if plans continue to deprecate them & browser venders may completely limit the former being possible with new things to limit the capabilities emerging specifications related to Web components allow you
Granted, it’s slightly more complicated than that; such things were made available for elements to be extended via newer components possible with the technologies that allow developer to create their own elements (Web Components).
The logic behind that was to enable the very same ability browser engineers had to create, modify, & style existing elements to allow for a more “democratic Web”; All existing elements be exposed to these new technologies for browser developers to better understand and modify them.
Elements almost impossible to style as desired today actually now easier to style completely to the author’s application needs without necessary having to resort to completely remaking them just to style them like the `video` & `button` element , form elements, & etc using Shadow DOM, shadow-piercing combinators as needed, & the other technologies that’ll enable developers to make our Web components
Developers can create their own Web components that they can campaign be shipped on most browsers in the future for more guaranteed functionality (that they’re available for developers & users without an active internet connection since they’d be shipping with the browser instead of being an external dependency through HTML imports or ES6 import syntax in the future)
However, until very recently, these technologies did *not* have explicit ways for them to limit the extent these new technologies can be used like limiting the “superpowers” of shadow-piercing combinators to create elements not unlike existing form elements in terms of dictating what can & cannot be done with them—including what styles cannot be overriden or touchable through these new tools.
For that reason, ideas of having a “Open/Closed” Shadow DOM, `@control`, & removing piercing combinators altogether have been proposed.
That’s where things get complicated.
While there’s legitimate concerns browser engineers can just use these tools arguably unnecessarily to cause us to have the very same problem we have with existing elements like “, “, form elements, & etc , I’d say it’s fair that component authors can *choose* to make custom elements that outsiders have very limited control on how they’re styled & how they behave.
There’s no easy solution to these issues, & why we have articles like this one instead of easily modifable buttons today.