Our CSS isn't opinionated enough - craigabbott.co.ukSkip to main content<br>Linkedin Icon@abbott567<br>Mastodon Icona11y.info/@craigabbott<br>Bluesky Icon@craigabbott@bsky.social
Our CSS isn't opinionated enough<br>For as long as I can remember, styling classes has always been pushed as "best practice" for writing CSS. Avoid IDs because they're too specific. Avoid styling tags directly because it's too broad.<br>You can't get away from it. It's even baked into most of the linting tools we use every day. It's sensible advice, but I do think it has quietly trained several generations of developers to build things that look correct, yet communicate no semantics.<br>The advice we've all been given<br>If you've ever read a CSS style guide, you've almost certainly seen specificity explained. The browser works out which rule wins based on how specific the selector is. An inline style beats an ID, an ID beats a class, and a class beats a tag.<br>Because IDs are so specific, they're a nightmare to override later. So the common advice is to avoid them for styling. And, because tag selectors are so broad, targeting the button directly might accidentally include buttons you didn't mean to. So, the advice is often just avoid those too and reach for a class every time. Even CSS linters often complain if you try to target anything except a class.<br>The "best practice" most of us were taught for a button, just looks something like the following example:<br>.btn {<br>padding: 0.75rem 1.5rem;<br>background: #0f7a52;<br>color: #ffffff;<br>box-shadow: 0 2px 0 #083d29;<br>}It's not that it's bad advice. It's actually really good advice, depending on which lens you view it through. Classes are reusable, they're easy to override and they keep specificity nice and flat. However, like in my post best practice is just your opinion, we have to remain conscious that "best practice" is exactly that, an opinion. This one has held up pretty well for a long time, but is it really the best way of doing things?<br>The issue is, a class is just a way to enforce rendered styling, it carries very little semantic meaning to a browser, to an assistive technology, or to anybody other than the developer who wrote it.<br>A class will happily lie to you and your users<br>Because a class is purely cosmetic, you can slap it on absolutely anything and it will happily apply all the styles. The browser doesn't care what element is underneath. I guess if I was to stretch for an analogy, we can think of a class more like paint than an item of clothing. Paint can be applied to literally anything, whereas clothing comes in set shapes and sizes, so they can only fit on certain things. For example, all three of these elements will look identical using a class after using a CSS reset, which is also common practice.<br>button class="btn">Submitbutton>
div class="btn" onclick="submitForm()">Submitdiv>
span class="btn" onclick="submitForm()">Submitspan>Visually, they all look like a real button. They have the padding, the background, the shadow, and they will all submit the form when you click on them using a mouse. However, only one of them is truly a button. Only one is focusable with a keyboard. Only one of them will fire on the Enter and Space keys. Only one of them will be announced as a button by a screen reader, and only one of them will respond to voice recognition software when you say "click button". And, they're all the one using the tag.<br>This is one of the most common failures you're likely to see in the wild. A generic element, like a or a , painted up to look like an interactive element, but with none of the behaviour or semantics that make it actually work. It fails 4.1.2 Name, Role, Value, because the role isn't communicated, and it usually fails 2.1.1 Keyboard too, because you can't operate it without using a mouse.<br>As an example, to make behave like an actual button, you'd need to manually add all of the same semantics. This would be a surprising amount of code by the time you're done. You'd need to manually code up the:<br>role attribute<br>tabindex attribute<br>onclick event<br>keydown event<br>keyup event<br>form.requestSubmit() method<br>Also, the events for Space and Enter are technically different, so you also need to account for that. You can read more about this in the following post, brief note on buttons, enter and space, by Adrian Roselli.<br>The class-first CSS approach didn't cause this, but it absolutely enables it! We've built and pushed a "best practice" way of working where appearance and semantic meaning are completely decoupled. Then, we're weirdly surprised that people are constantly shipping products with the styles and semantics completely decoupled!<br>What if the styles were reliant on the semantics?<br>Now. What if we only applied button styles to actual buttons? I know! Crazy, right?<br>For example:<br>button {<br>padding: 0.75rem 1.5rem;<br>background: #0f7a52;<br>color: #ffffff;<br>box-shadow: 0 2px 0 #083d29;<br>}Now, if a developer wants something to look like a button, the only way to actually get those styles is to...