Let’s talk about state. Communicating state to the user that is, not application stores state in JavaScript objects, or localStorage. We’re going to be talking about how to let our users know about state (think: whether a button is disabled or not, or if a panel is active or not), and how we can use CSS for that. We’re not going to be using inline styles, or, as much as can be helped, class selectors, for reasons that will become clear as we go.
Still here? Cool. Let’s do this.
All dynamic components of an application have a default user-facing state, and that state needs to be stored and updated as users interact with these components.
For example, when a button is pressed, things happen (that’s what buttons are for). When these things happen, they are typically represented in a visual manner in the interface. The button’s background may change to indicate it was pressed. If the button controls other components in the interface, those components likely visually change in style, or in some cases their visibility is toggled. An item gets deleted, a notification pops up, an error style is applied, etc.
You may have noticed that we’ve been mentioning the “visual” state of components quite a bit. That’s exactly the kind of problem I’ve been finding with a lot of tutorials, articles and general talking about state.
More often than not, developers are using “stateful” classes to manage a component’s state. But this is sorely inadequate, as a component is composed of more than just how it looks. There are underlying semantics that need to be managed along with the component’s visual representation. The failure to manage those underlying semantics becomes apparent as soon as you interact with it via keyboard and/or screen reader.
This is an article about appropriately conveying state so that users, beyond sighted, mouse-using users, can interact with our interfaces.
State is more than just how it looks
Outside of using CSS to appropriately hide content from sighted users and assistive technologies, CSS doesn’t have many intentional effects on an element’s semantics or accessible state. What I mean by that is outside of properties like the unsupported ‘speak’, before/after pseudo content, and media queries to specifically restyle components based on user preferences, like the Reduced Motion Media Query and other proposed User Queries, CSS alone is not meant to change an element’s semantics, content or appropriately convey the state of an element in a meaningful way.
Why do I bring all this up? Because managing state with CSS classes alone is, mostly, inadequate for conveying state to all users. Being a language for presentational purposes, giving an input a class of .has-error
to change the border color to a shade of red, has no semantic value to it. For all CSS cares, “That’s how you wanted to style that input. Cool. I got your back! Just don’t ask me to style upwards in the DOM. I draw the line there, buddy…”
Instead, to manage and convey state, we should be updating attributes on the appropriate elements. And no, I don’t mean data-attributes. Those don’t mean anything either. If we take this approach, in many cases we won’t even need stateful classes, outside of classes that toggle an element’s display
.
Did we forget we can style with attribute selectors?
HTML and ARIA have a whole bunch of attributes that should be used to appropriately convey the current state of a component.
Thinking about using an .is-disabled
class on your <button></button>
or <input type="text" />
? That’s only going to visually disable it. You’re still going to have to programmatically turn off click and keyboard events to that element. Instead use the [disabled]
attribute and you’ll have yourself a CSS selector to style your element, and the browser will do all the appropriate work to disable that element for you!
So instead of:
input.is-disabled {
opacity: .65;
}
Which only visually modifies an input, use:
input[disabled] {
opacity: .65;
}
This achieves the same visual effect as using the .is-disabled
class, but instead we’re utilizing the attribute selector from the attribute we need to set to convey the current state to the browser and users. All while not having to do any of the aforementioned extra work, with JavaScript, to disable the input, if we were simply toggling a class.
Example: Being “Active”
To provide some deeper context, let’s look at a situation where you might use an .is-active
class. For different components, being “active” can mean completely different things, which is why I can appreciate wanting to use a single, reusable class name, instead of determining which attribute needs to be managed to appropriately convey state. But making state management easier for developers doesn’t necessarily help users, so let’s do this the right way.
Active Navigation Links
First let’s look at declaring the currently active link in a navigation. The following Pen has two examples. The first using an .is-active
class to indicate the current navigation item. The second uses aria-current="page"
.
See the Pen .is-active vs aria-current=’page’ by Scott (@scottohara) on CodePen.
While they both look exactly the same, if you use either Jaws 18, Voice Over, or NVDA 2017.2 (when it’s released) when navigating the example, you’ll hear something like: “Features, current page.” when interacting with the example using aria-current
. Check out Léonie Watson’s article on [aria-current]
for many other examples of where one could use this attribute for styling, in place of an .is-active
class.
Active Buttons
Depending on the purpose of the button, the active state of the button may need to be augmented for screen reader users via one of the following ARIA attributes:
aria-expanded
– indicates that the button controls another component in the interface, and relays that component’s current state.aria-pressed
– indicates that the button behaves similarly to a checkbox, in that it has its state toggles between being pressed or unpressed.
Without using one of the above attributes, a button has no inherent way of communicating whether it had been interacted with or not. That is totally fine if a situation doesn’t require it, but if you do need to communicate a button has been activated, then here’s how we can do that using aria-pressed
:
See the Pen Toggle Button Example by Scott (@scottohara) on CodePen.
In the above example, we have a button that can be interacted with to add an item to a shopping cart. To indicate when an item has been added, instead of using a class, we’re instead toggling the boolean value of the aria-pressed
attribute, and using the [aria-pressed="true"]
as our styling hook to visually convey the active state. When interacting with the button via a screen reader, it will be announced as “checked” or “unchecked”, add to cart, toggle button.
For a deep dive into the considerations, one should take when developing accessible toggle buttons, one need look no further than Heydon Pickering’s Toggle Buttons article. Heydon outlines, in great detail, why it’s not a good idea to change the visible label of the button, and even brings to light that you may not actually want a toggle button, but instead should consider using a switch.
Managing Accordion State
For our final example, let’s take a look at how we’d manage state in an accordion component:
See the Pen ARIA Accordion Example by Scott (@scottohara) on CodePen.
If you read through the comments in the CSS and JavaScript, you’ll note that this demo is doing a few things.
First, the markup pattern of the accordion is built in a way so that if JavaScript ever becomes disabled for any reason, none of the content will be inaccessible to those users, as the panels are only hidden if the .js
class is present.
Second, to circumvent the need for actual <button></button>
elements within each accordion panel heading, we’re instead converting their nested <a>
s into “buttons”, by applying the ARIA role="button"
, and then adding in all the expected keyboard functionality via the keydown event listener. Additionally, to ensure the “button” can be accessed by keyboard users, a tabindex="0"
has been set to each of the ARIA buttons.
Finally, here we use the aria-expanded
attribute to communicate the current state of the accordion panel, so when a user focuses on the accordion trigger with a screen reader, it will announce “Accordion Heading, collapsed (or expanded) button”.
You will notice that the accordion panels are utilizing an .is-active
class to toggle their visible state. Egads! But wait, this is the one thing we can count on CSS alone to help us with. If we take a closer look at the selectors at work here:
.js .accordion__panel {
border-bottom: 1px solid;
overflow: hidden;
padding: 0 1em;
max-height: 0px;
transition:
max-height .2s ease-in-out,
visibility .2s ease-in-out;
visibility: hidden;
}
.js .accordion__panel.is-active {
max-height: 100vh;
visibility: visible;
}
The first selector, the one contingent on JavaScript being available, utilizes visibility: hidden
to inclusively hide the panel’s contents from both sighted users and users of assistive technologies. The overflow, max-height, and transition properties are then set to collapse the panel, and prepare it to grow into it’s expanded form, once the .is-active
class is added to the accordion panel. I could have toggled display: none
or programmatically added and removed the hidden
attribute from the panels, instead, but we would have lost out on the ability to transition the panel open. And everyone likes a good transition, right?
In Closing
The main thing I want you to take away from all of this is that if you are only toggling classes to visually manage state of your components, you are likely not appropriately conveying that state to users of assistive technologies.
You need to be using the appropriate elements (<button></button>
s are your friend!), and managing the appropriate attributes and their values to make truly accessible user experiences. Sure, you could to do those things and continue to toggle stateful classes to control your styling. But if we have to update attributes and their values, and those are also valid CSS selectors, then why would we do more work than needed by toggling classes too?
Awesome article Scott.
I’m in the process of reviewing a bunch of our accessibility code – and especially stuff like accordions, tabs, nav bars – so will seriously consider removing “is-state” classes and use appropriate attribute selectors instead.
And Heydon Pickering’s article laid the groundwork for a toggle button POC – http://codepen.io/basherkev/pen/jmZpyv – that I have now incorporated into our UI component library.
Also really glad to see so many great A11Y articles and resources appearing recently.
Thank you!
Great information, I work as a web developer and our company is taking big steps to make sure all our services/sites are WCAG 2.0 AA compliant. After reading this article I used a combination of aria-controls and aria-expanded to handle our navigation interaction. (classes were used previously)
Not only is the menu more accessible now, the code behind it is more semantic and easier to understand.
That’s great Shane! Glad it was helpful for you.
I like the idea of this article and I think it was very well-presented, but I have a minor objection. The premise of the article seems to be this:
The problem with that is, CSS classes are by far the most scalable way to style stuff. Are you familiar with SMACCS?
https://smacss.com/
While I agree that we should all strive to set the proper attributes for state, I think it’s worth also toggling classes so as to maintain a scalable stylesheet on large projects.
Hey Scott,
While you’re partially correct in that I am advocating for utilizing the attribute selectors that are available to us when we appropriately update the user-facing state on our components, I would submit that the premise of the article is really “Build things in an accessible way and you’ll get a lot of stateful styling hooks for free.” And as I sum up in my closing: Since we get these hooks for free, why do extra work of ALSO bringing additional classes and JS into the mix and have to manage that too?
As far as SMACSS goes, I’m quite familiar with it. I appreciate and like most of the ideas behind it, but I also don’t fully submit to it, or any single CSS methodology. I personally think that CSS is a deep language that has a lot to offer, and shouldn’t be confined to a single type of selector ‘because it scales the best.’ The different selectors are there for reasons, if we collectively get better at utilizing them, then maybe a lot of these ‘CSS is broken’ conversations will actually sort themselves out :)
But, I honestly see no (scalable) difference between
.tab-is-open { /* from smacss website */ }
vs
.tab[aria-expanded=”true”] {}
The former does nothing to educate junior developers that there’s more to communicating state than simply visually swapping some colors around. While the latter should make them think “What is this? I should learn what this means.”
Regardless, I could literally write an entire sub-article about this here in the comments, but I would rather just talk to you about it instead. So open invite here to reach out to me and we can continue this discussion if you’d like.
Thank you :)
So, why is SMACCS valuable? Two reasons. Semantic selectors, and weak selectors. I’d agree that your methods in this article are supremely semantic. Kudos. But class-based selectors are semantic enough, and far weaker. In the example you’re citing,
.tab-is-open { /* from smacss website */ }
vs.tab[aria-expanded=”true”] {}
the difference is scalability is obvious: The SMACCS version is “twice” as scalable because it’s half as strong (humor my fictional scalability … ummm … scale). The point it, SMACCS is using a single class name, and you’re using a class plus an attribute. You’re that much further down the path towards!important
, and there lies madness.Keeping selectors weak is among the very most important concerns in a large project. I’m not going to be dogmatic and say it’s the most important concern, but damn it’s way up there.
Hey Scott,
Your response fails to reference this portion of the SMACSS website concerning stateful classes:
“Since the state will likely need to override the style of a more complex rule set, the use of !important is allowed and, dare I say, recommended. (I used to say that !important was never needed but on complex systems, it is often a necessity.)”
Per your critique of the example I used, what are you trying to scale there? Why would one use a ‘tab-is-open’ class on anything beyond the original ‘.tab’ element? How is using the more strict selector a poor choice here, if that stateful class has no business being used on any other component, and it’s supposed to overwrite the default styling of the tab?
Even something more generic like a ‘reusable’ is-open class. What context is that being used in? A tab, an off-screen menu, a tooltip, an accordion? I would submit styling for each of those would need to be different based on whether they required a CSS transition or not, or if the original state of the content was truly hidden from all users, or if the content was merely visually hidden (think a drop menu needing an on click event to open, vs a drop menu that should be opened on hover/focus of the main trigger, or any of the drop menu’s child elements.)
So what’s scalable about a reusable class that isn’t actually reusable beyond it’s name? Name recognition? Get familiar with attribute selectors like [disabled] and [aria-expanded] instead, since those actually mean something and are often neglected in favor of stateful classes.
Finally: “But class-based selectors are semantic enough…”
As you should know from reading the article, I disagree with this point of view. I think it’s far more important for developers to build things in a smart, accessible way and utilize all the tools at our disposal to do so.
Again, thanks for the reply, since these are topics I chose not to get too deep into within the article, because I didn’t want it to be mistaken as me dissing on SMACSS. Again, I think SMACSS, overall, is an excellent methodology to CSS… but I can think that and still take issue with one aspect of it, and instead advocate for doing it in a way that achieves similar styling effects, but by using selectors that have actual semantics behind them. As I reference in my closing comment, you can keep using stateful classes if you want, along with updating a components attributes to truly communicate state. I personally think you’re just doing extra work because of it.
Re: !important. Yup. I agree with that too. But the point I was making with that quote was that state classes should be more specific. You’re focusing on the !important part and disregarding the fact it’s ok to have stronger selectors when you’re overwriting default state.
As for your counter example, It’s hard to argue against a vague scenario like that, because while you’re saying it’d be insufficient, I’m thinking of similar scenarios I’ve been in where it’s worked out perfectly fine, but to adequately talk about it here, there’d need to be significantly more context into the behavior of the markup elements and user expectations of the interactions.
And I’m glad we’re not arguing about the management of attributes, since that’s the most important thing here. But since I’ve already admitted I don’t strictly follow SMACSS, I’m not sure what’s to gain by continuing this thread? You don’t agree with my point of view on CSS selectors, and that’s fine. You agree that it’s more up front work to manage both classes and attributes. OK…
Seems we’re merely debating CSS methodologies here, and that’s not really going to go anywhere. Because for every example you throw at me, I could continue to throw examples back at you to support my stance.
So with that I sincerely say take care Scott, and thanks for the discussion :)
Touchè, he does acknowledge that
!important
is not the worst-case scenario, but the very next sentence is, “Leave !important off until you actually and truly need it”. If you make weak selectors a top priority, you won’t need it.Your example with the tab-is-open scenario threw me for a second, because I agree that on first glance it seems like your attribute selector will never need to be over-ridden, making selector strength a moot point. However, I have a counter example in my work right now that I think is pretty common: A row of tabs where most fire a drop-down menu, but one fires a search box, and they get very different styles upon doing so. It seems clear to me that attribute selectors alone would be insufficient here. You need to style via classes of some kind, and by involving attribute selectors, you’re unnecessarily increasing the strength of your selection.
I’m certainly not arguing against managing your attributes as state changes, I just believe from personal experience that it’s worthwhile to manage classes as well. You’re calling it extra work, and up front it definitely is. But it pays dividends as a project ages and grows, and you can gleefully add or alter modules without being in a selector war.
Right on, thanks for the chat and the thought-provoking article.
I recommend using summary and Details for accordion – like all the other HTML elements they have built-in accessibility – I know MS is still not supporting them. But on the one Hand the fall back is working (just boxes). Besides there are polyfills – I’d recommend https://mathiasbynens.be/notes/html5-details-jquery
They even come with the “open” Attribute included which you can use for both: styling and further JS-improvements. Whetcher working with Js or not: summary and details are the elements you want to use for expandable Content, like should be used for buttons or hx for headings. Always!
Hi Marc,
Good call on the details/summary usage. If the content dictates that would be the appropriate markup pattern to use, then it should definitely be the go to, w/polyfill of course.
I wanted to focus on the ARIA accordion as it made more sense in my argument to toggle attributes rather than classes, with JavaScript. A details/summary wouldn’t (shouldn’t) require any JavaScript or class toggling, so while it may be a suitable component to use, it didn’t really address the issue I wanted to highlight of JS components not conveying state correctly.
Hope that clears up any questions you may have had on why I chose to go in that direction.
Thanks!
You’re right – I didn’t think about what you wanted to explain and of course I know, that there are project needs, that make it necessary to use other elements than details/summary – sometimes more meaningful. Besides details/summary has one important drawback: in my opinion it is more of a presentational markup than semantics. Quite often I would actually like to use article/hx or something else rather than details/summary.
Anyway it seems to me, that some developers just forgot about these elements – maybe there was a poor browser support for such a long time and developers became too familiar with jquery plugins for accordions, which should be a workaround but became a comfortable part of there toolbox.
So I think mentioning details/summary time after time is a good idea – especially when it comes to inclusive design. – If you want to build an accordion which allows only one detail to be opened JS is necessary anyway. But again it is very convenient, that you can use (and manipulate) the open attribute.
Hopes that can help some guys to clean up there toolbox a little bit ;-)