When I start working on an inherited code base, one of the first things I find helpful to do is an HTML validation audit. The audit helps me get familiarized with the code base while doing some often much-needed clean up work. One issue that usually crops up more than once has to do with improper nesting, which can have unintended effects on accessibility and page rendering.
Consider the following design in which each button would be wired up to toggle a pane of additional information for each person:
<display: flex;> on the
<button> in order to use
align-items: center; and
justify-content: space-between;, something must be used to group
.button__details to keep them stacked. A
<div> is a quick tool for grouping and it’s exactly what I find in my audits. It seems innocuous enough, but if you plug the HTML into an HTML validator, you get the following:
Why Does HTML Validation Matter?
At this point, you might be thinking, "the design was achieved in the browser and users can interact with elements as intended. Why can’t we just move on?”
HTML validation is more than a fringe concern. Validation ensures browser and device compatibility—even for combinations you’re not testing for. This is certainly not limited to Operating System and mobile devices. Indeed, assistive technology vastly benefits from error-less semantic HTML. It also safeguards from issues that would crop up in the future if a browser decides to stick closer to compliance.
Checking for validation also assures that search engines aren’t missing any of your site or app’s content. Search engines all behave a bit differently, but given that they are programs, they have to make preset decisions on what to do with errors. Rather than hoping that these errors are ignored, the best practice here is to ensure error-free markup so the engines can index cleanly and move on.
So Why is a Div Not Allowed?
Back to our original error, we could trial-and-error it until we don’t get an error, but let’s step back for a minute and investigate just what the issue is. If a simple tag like a
<div> won’t work, there must be something bigger we need to understand. If we take a look a the HTML spec on buttons, we see the following:
We’re getting an error about child content, so pay special attention to the "Content model” list item. Note that its contents read "phrasing content, but there must be no interactive content descendant.” "Interactive content” refers to a link or an input, which of course makes sense because clicking on a button does something. Having a child that also does something would be problematic. Of course a
<div> isn’t interactive, though, so our content problem must come from the "phrasing content” portion of this statement.
I think we’re getting somewhere.
There’s a lot to go over with HTML content categories, but this article will focus primarily on phrasing and flow. If you’d like to look further into other categories, the HTML spec and MDN are wonderful resources.
Flow content is a big bucket into which almost all HTML elements fall. Virtually any HTML element that is used within the
<body> can be at least categorized as flow content. I say "at least,” as many elements fall under more than one category. For example, an
<h1> is both flow and heading content.
<footer> elements are both flow and sectioning content.
<div>, however, only lives within the flow category. This might not come as much surprise, since a
<div> gives us no semantic information about the content within it. It is merely a container of flow content.
Phrasing content represents the text within an HTML document. Just as multiple sentences joined together create a paragraph in prose, multiple groups of phrasing content form paragraphs in HTML.
An important thing to note about phrasing content is that many elements in this category only allow phrasing content as children. Meaning, an element that is categorized as phrasing content (e.g.
<span>) cannot contain flow content (
<div>), heading content (
<h2>), or any other content types (
<form>). This is particularly relevant for our current
<button> exploration. One notable exception is an
<a>, which can contain both flow and phrasing content. Adding flow content within an
<a>, though, means that it can no longer live within other phrasing content.
Solve the Problem
Now that we understand the context of the issue, we are more equipped to solve the problem—and hopefully prevent it from happening again.
<button>s—and indeed most elements of the phrasing content model—can’t accept flow content, so the
<div> breaks HTML validation. A
<span> functions as a container for phrasing content, so interchanges perfectly in this context.
When building for accessibility, the best tools in our toolbox are HTML semantic elements. When describing our content, it’s equally important to remember the constraints of the semantic elements we choose. As we’ve seen here, a failure to recognize the importance of content categories can lead to some unintended consequences.
It’s also good to remember that the issue we saw here isn’t just scoped to
<button>s. As noted, any element that only allows for phrasing content will not allow for a div child. Examples of this include headings (
<strong> and many others.
One good practice to adopt in order to prevent the errors represented in this investigation is to run an HTML validation check during every markup-related PR. Staying in front of these errors can help save you a ton of time and prevent issues down the road.
DockYard is a digital product agency offering custom software, mobile, and web application development consulting. We provide exceptional professional services in strategy, user experience, design, and full stack engineering using Ember.js, React.js, Ruby, and Elixir. With a nationwide staff, we’ve got consultants in key markets across the United States, including San Francisco, Los Angeles, Denver, Chicago, Austin, New York, and Boston.