Dot matrix chart via CSS attributes

In this chapter, we're going to create a dot matrix chart, in which a grid of dots is colored to correspond with a category. These kinds of charts are often used to show proportions of different category groups within a population, such as gender/race or job sector. We're going to do one better: our matrix is going to have controls letting a user toggle between two views, each with its own categories. Here's what the final result should look like:

Surprisingly, the JavaScript code used to set up this example is less than 20 lines, and it's not out of misplaced minimalism (feel free to use the dev tools to check it out). Instead, we're mostly leveraging some basic, if obscure, features of HTML and CSS:

In the resulting visualization, the browser does most of the work for us, and it's easy to add additional dimensions to the matrix. Having that kind of flexibility is really handy while prototyping, especially if you don't yet know which categories will be editorially significant.

There can be only one

There's a truism in the accessibility community: don't reinvent the select element. The form controls available in the browser may be clumsy, and difficult to restyle, but they also provide a large amount of benefit to screen readers, to people with reduced dexterity, and to users accustomed to the default behavior on their particular operating system. Reproducing all of that is an enormous investment, as this guide to creating a "custom checkbox" shows. The built-in defaults aren't as pretty, but there's good reason to just use them instead of doing all that work.

However, I would go one step farther: for many common UI patterns, there may be a form element that we can build off, even if it's not an obvious choice. This shouldn't be surprising, since the point of "UI patterns" is to look for behaviors that crop up across multiple different widgets. Take, for example, a tabbed window and an image gallery with a "filmstrip" of thumbnails underneath. They have very different goals, and they look very different to users. But the functionality, in which the selected item is shown while the others are hidden, is largely the same, and the underlying code for them might be almost identical.

In this case, what we're going to leverage is the radio button, one of the several types available for the input tag. Radio buttons are basically checkboxes, but they're linked together in groups, and only one can be checked at a time. Checking a different radio button will uncheck the first. Each button in a group can have its own value, and the group itself is created by a common value for the "name" attribute. Here's a set of three radio buttons in a group, each with its own label:

<div class="radio-block">
  <input type="radio" name="size" value="S" id="size-S" checked="checked">
  <label for="size-S">Small</label>
  <input type="radio" name="size" value="M" id="size-M">
  <label for="size-M">Medium</label>
  <input type="radio" name="size" value="L" id="size-L">
  <label for="size-L">Large</label>
</div>

By giving each input its own ID, and setting the "for" attribute of each label to match, the browser hooks up each label to the corresponding input: clicking on the label text will select that radio button from the group. Essentially, it makes the label a part of the input.

Now, with a little CSS trickery, we can make actually hide the inputs themselves, and instead show the checked/unchecked state on the label. To do so, we'll use the + CSS operator, which selects an element's next sibling, and the :checked pseudoclass. Here's some style rules that will highlight the label for the selected item in red:

.radio-block input { opacity: 0; width: 0 }
.radio-block input + label {
  display: inline-block;
  padding: 2px 4px;
  background: white;
  color: black;
  cursor: pointer;
}
.radio-block input:focus + label {
  outline: 1px solid blue;
}
.radio-block input:checked + label {
  background: red;
  color: white;
}

And here it is in action:

Add a few extra styles, and it's easy to make this look like an iOS-style "pillbox," a set of Android tabs, a line of gallery thumbnails, or any other UI widget where the user can select only one item from a set of choices. We didn't have to write any JS code to keep track of the selected item and update the classes on those elements. And because we're just using HTML and CSS, this code is accessible to screen readers, and you can easily use the arrow keys to pick between the different options (although adding the :focus rule is necessary to make that more clear).

It's worth experimenting with this technique, since it can also be generalized to checkboxes as well. But that's a topic for study on your own time—now that our controls are working, let's hook them up to our category palettes.

Your best attributes

Each dot in our matrix represents one person. Each person can have multiple category groups assigned, but only one within a group. So, for example, in our interactive, people can choose "big" or "small" in category A, and they can vote "yes" or "no" for category B. For this interactive, I'm creating 100 of them at random. Then I create the dots using a template string, and simply add classes that match the choices they've made for each category:

var html = "";
dots.forEach(function(d) {
  html += `<div class="dot ${d.a} ${d.b}"></div>`;
});
grid.innerHTML = html;

Our dots are styled to be 30px by 30px inline blocks, and setting the border radius to 50% or more makes them circular. But where do they get their colors? For that, we set an attribute on the container element, and then use an attribute selector to match:

[data-category="a"] .big { background: green }
[data-category="a"] .small { background: red }
[data-category="b"] .yes { background: orange }
[data-category="b"] .no { background: purple }

Attribute selectors are written between square brackets, and are extremely flexible in how they match. These selectors check for the exact value of the attribute, but there are many other options available:

A little experimentation creates all kinds of useful applications for these selectors, especially when combined with the :not() pseudoclass. For example, you might style links to external sites with a little indicator by combining for a[href*="://"] and :not([href*="yourdomain.com"]), or links that open in a new window via a[target]. You could blank out image tags that don't have a src attribute, hiding the "broken image" icon. Or use this to hide UI widgets that haven't loaded until they have a "ready" attribute added. Indeed, because these selectors are more low-level than class and ID selectors, they open up a lot more power.

To look back at our chart, however, we're simply allowing each dot to color itself based on the current "filter" of the container, as expressed by its "data-category" attribute. We don't need to set each element's color individually—they can have as many category classes as they want, but only one will be expressed at any time. And because the parent element is controlled using an attribute value instead of a class, we don't have to worry about removing old state when we switch filters. We can just clobber the attribute value with the new setting, and watch the colors adjust:

controls.addEventListener("change", function() {
  //find the radio button that's currently checked
  var value = $.one(`input[name="dot-category"]:checked`).value;
  container.dataset.category = value;
});

It's this modal nature that has made attributes one of the most useful tools in my CSS toolkit. Compared to re-coloring the dots via JavaScript, or trying to set only one category class, this code is clean, simple, and reliable in any number of other situations. Remember this technique whenever you need to add, remove, or update a large number of DOM elements in response to a single updated value, like a system mode or filter.