HTML and CSS Can Solve Many Problems, but JS Is Still Essential
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.
- Item1
- Item2
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
:focusor listening to click events to allow users to trigger the dropdown menu - Adding
aria-expandedto 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
- Item1
- Item2
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-labelto let screen readers know the purpose of the checkbox or radio button (or usearia-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.
Information
This photo is taken by Unsplash Clay Banks
Tokyo
This photo is taken by Unplash Jezael Melgoza.
I like Tokyo because it's a such good place where I can see people meltdown
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
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.