· 8 min read

HTML and CSS Can Solve Many Problems, but JS Is Still Essential

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Back in 2017, I read an article titled Effective Frontend 1: Don’t Use JS for Problems That Can Be Solved with HTML/CSS (Chinese). Reading it for the first time, I resonated deeply with it and learned many techniques I was unfamiliar with at the time, and I highly recommend checking it out. While JavaScript can solve almost any problem, solving things with CSS is definitely better from the perspectives of accessibility, performance, and bundle size. However, avoiding JS as much as possible doesn’t mean never using it at all; there is a distinction between the two. In this article, I revisit that post and highlight a few areas where I believe improvements can be made.

Using :hover for Cue Styles

Indeed, using :hover to indicate to users that a UI component is interactive is practically common sense for frontend engineers. The original article also mentioned that you can achieve a dropdown menu effect using :hover.

Switching to display: block on hover, and display: none by default. This seems fine on the surface, but what if the user isn’t navigating with a mouse? If a user navigates via keyboard, :hover has no effect. Furthermore, it is constrained by the DOM structure, requiring the UI element that triggers the menu to be adjacent to the dropdown list itself.

Therefore, my recommendation when using :hover to hint that an element is interactive is to consider how users should interact with it if they aren’t using a mouse. This includes:

  • Adding :focus or listening to click events to allow users to trigger the dropdown menu
  • Adding aria-expanded to inform screen readers of the menu’s current open/closed state, and adding keyboard navigation so users can navigate options with the up and down arrow keys

In this example, besides triggering the dropdown via hover, JavaScript is also used to listen to focus and mouseover events to update aria-expanded. Keyboard navigation isn’t implemented here as it falls outside the scope of this article. As an aside, if you use aria-expanded, you can also adjust the CSS to:

.dropdown-item:hover + .item,
.item[aria-expanded="true"]
{
  /* style */
}

Custom Styles via :checked and Adjacent Sibling Combinators

If you want to implement custom checkboxes or radio buttons, you will likely need the technique mentioned in the article: combining pseudo-classes with adjacent sibling combinators makes it easy to build custom checkboxes.

The benefit of using :checked is that we don’t need to register additional event listeners to toggle classes. When building custom checkboxes or radio buttons, this approach should be preferred whenever possible over building one from scratch using a <div>. Not only does a custom <div> require considering many details, but its usability might not even match that of a slightly ugly, but at least functional, native checkbox.

However, there are several things to keep in mind with this approach:

  • Use aria-label to let screen readers know the purpose of the checkbox or radio button (or use aria-labelledby).
  • Use <div role="status"></div> or other methods to announce value changes (if necessary).
  • Implement focus state styling.

When screen readers encounter a checkbox, they only read out the label name and whether it is checked. If the checkbox’s purpose isn’t just a simple checked/unchecked state (for instance, a dark mode toggle), adding extra cues makes it much easier for screen reader users to understand.

To hide the input while keeping it focusable, instead of using display: none directly, other CSS properties are used to visually hide it. At the same time, to show a focus style during keyboard navigation, :focus-visible is added here. This way, the rule only applies when navigating with the keyboard (like pressing Tab), and you won’t see a focus outline appear when clicking with a mouse.

Equal-Height Multi-Column Layouts

That article was written back in 2016. While the methods described there work, they are a bit old-school. In 2022, with flexbox support being virtually universal, we can solve this directly using flexbox—or CSS Grid if finer-grained control is needed.

The mechanism relies on flex layout behavior: align-items defaults to stretch, so the container’s height adjusts to match the tallest item in the row. One thing to note is that if you need multiple rows, remember to add flex-wrap: wrap; otherwise, flexbox will attempt to squeeze everything into a single row by default.

Form Submission

I strongly agree with the author’s point that many people overlook the fact that the native <form> element has existed for decades. Defining form content using <form> can save a massive amount of JavaScript code.

The article mentioned that you can use the browser’s native form validation combined with the :invalid pseudo-class for styling; in the example, the submit button is styled with opacity: 0.5 when in an invalid state. However, likely just for demonstration purposes, the author used a <span> instead of a <button>. In actual implementation, you should use a <button> and use JavaScript to add disabled when input values are invalid.

If you aren’t familiar with forms yet, you can refer to these two articles:

Making Good Use of Pseudo-Classes

The author mentioned pseudo-classes like :checked, :focus, :invalid, and so forth. Making good use of these pseudo-classes can eliminate unnecessary JavaScript and make the code easier to read.

Regarding pseudo-classes, I also wrote an article introducing some relatively newer ones. Feel free to check it out if you’re interested: Useful CSS Pseudo-Classes for Layout.

Conclusion

2017 happened to be right around the time I first got into frontend development, and my grasp of the finer details wasn’t very strong yet. Looking back now, I realize that implementing a UI with great user experience requires considering many subtle details—it’s not as simple as just slapping on some CSS and calling it a day. In many cases, JavaScript remains indispensable for accessibility considerations.

Related Posts

Explore Other Topics