One of a few articles I’m writing about accessibility. The previous one was about color contrast.
Last time I wrote about accessibility, I complained about a common accessibility test that I think is inaccurate and counterproductive: color contrast. This time the problem was just my mistake.
The takeaway: if you want an element on a website to have an action when you click it, it should probably be a button.
Buttons have useful default behavior
onclick handler. So your
<div> element can turn from static UI to an interactive element just by adding that behavior. Unfortunately, this only adds one particular type of interaction to the element: the ability to respond to a click.
onclick handler does little to signal that the element is interactive, because lots of elements (sometimes all elements) have
onclick handlers, and only a subset of those elements are really click targets.
<button> elements, on the other hand, are always obviously clickable (and non-clickable, when they have a
disabled property). They also have useful default behaviors:
- You can
Tabto focus a button on a webpage. Buttons are, by default, part of the page’s tab order.
- When a
buttonis focused, it has a default style that shows its focus - usually a blue ring if you’re using macOS.
- When a
buttonis focused, you can hit
Spaceto activate it via the keyboard.
Why I strayed
ObservableHQ ended up with a lot of
<a> elements where it should have had buttons. In part, this was refactoring debt. But it was also from another cause: the user agent stylesheet.
The user agent stylesheet is a built-in set of styles that are the defaults for elements on a webpage. They vary between browsers.
Now, to dive in really quickly, this goes back to the founding principles of the web and how they don’t line up to web expectations today. The idea was that individual users would be able to set their favorite fonts and read webpages in those fonts. Same with background color and font color, and other particulars. Part of the ‘cascade’ in Cascading Style Sheets was cascading from user choices.
This is an admirable idea! But it reflects two big differences between the early web and today’s web:
- Standards of polish and presentation are vastly different today than they were in the early web. With the exception of the personal websites of Computer Science professors, most webpages have very opinionated style choices.
- Web applications have an entirely different set of design constraints than blogs or papers. When an application needs to squeeze five buttons into a tiny UI space, or align different UIs perfectly, arbitrary variance in font size, face, and other parameters can easily break the layout.
And in this lens, buttons are a severe annoyance. The default user stylesheet for buttons in Chrome, for example, resets the font in the button to
-system-ui. The user style for a button looks something like:
font: 400 11px system-ui;
padding: 2px 6px 3px;
That’s a lot of defaults. And in different browsers, different defaults. Compared to the default style of a
This is part of my lame excuse for why ObservableHQ dodged buttons: their styling on the web is a weird attempt to make them seem like ‘native UI’ even in an age when our expectation is that web pages look uniform across platforms. Resetting all the defaults is a hassle and it’s easy to get wrong - just look at the many sites that use a nice system font stack but still have bits of Helvetica in their buttons.
How to go without buttons
When you switch to buttons, you pay with CSS styles. But you can go the other route, of using
<div> elements (or any other element), and making them accessible and adding the same affordances. But it’s arguably a bit trickier. You’ll need
role=button, to make it noticeable as a button
focusindex=0, to make it keyboard-focusable
- Event handlers for Enter & Space keys to provide the keyboard affordances.
MDN has a very helpful page on this process, with role=button. From my perspective, this is the hard way. Checking out some of the sites I rely on, I found a few that use role=button, like are.na - but are.na doesn’t have a visible focus state, and doesn’t have keyboard handlers. Instapaper, unfortunately, has a bunch of interactive but non-focusable, non-detectable elements.
My last post in this area was about color contrast, a property which automated tests can detect and do eagerly, constantly dole out disapprovals. In stark contrast, interactivity - and, I fear, a lot of the properties of web applications, as I will contrast with websites, brochureware, and blogs - are not easily testable. Google’s Lighthouse and the axe extension found plenty of problems with the site - most of which we’ve fixed - but our clickable divs weren’t one of them.
This is the latest piece in my day-to-day effort to improve my work. It’s not advanced technique or particularly novel, but might be useful to other folks building applications.
Observable is a lot better than it was a few months ago, but there are still big challenges ahead, some of which are going to require novel approaches. For example, two things we build the product on are iframes - which are the environment for running code - and CodeMirror, which is the editor for writing code. Navigating in and out of these abstractions is tricky, when, for example,
Tab indents text in CodeMirror, instead of moving on to the next element.
But that’s for a future blog post.
The other thing is that technically this is more universal design at this point than capital-a accessibility: we’re making sure that the interactivity of the application is broad and obvious, in part by making sure pages are keyboard-accessible. And it’s elementary at that - there’s a lot more to do.