The Correct way to Toggle Display with JavaScript
Exploring bugs in the traditional show/hide methods along with fixes to toggle display with JavaScript correctly at any given event.
Showing and hiding of elements on the Web has changed drastically in the past few years, especially after the introduction of JavaScript frameworks.
Let’s cover some traditional as well as modern ways to toggle display with JavaScript, while being mindful of accessibility and animations.
Traditional JavaScript display toggling
If you are making web apps for a while now, you know this technique pretty well. This is the classic one-liner that you could plug into your markup and get going.
const toggleDisplay = (target, display = "block") =>
(target.style.display =
window.getComputedStyle(target).display == "none" ? display : "none");
Doesn’t matter if:
- the element is already hidden
- its default display is flex, inline-flex, grid etc.
Accessibility with display toggles
Here’s a sample accessible implementation of this technique using ARIA, where we should add the aria-controls attribute to specify what the button actually controls.
We will also need to tweak the JavaScript to handle the aria-expanded attribute:
<script>
const toggleDisplay = (
target,
trigger,
display = "block"
) => {
const appliedDisplay =
window.getComputedStyle(target).display;
const isHidden = appliedDisplay === "none";
target.style.display = isHidden
? display
: "none";
trigger.setAttribute(
"aria-expanded",
String(isHidden)
);
};
</script>
<div class="flex gap-2" id="product-filters">...</div>
<button
type="button"
aria-controls="product-filters"
onclick="toggleDisplay(document.getElementById('product-filters'), this, 'flex')">
Toggle Filters
</button>
This ARIA attributes approach is standard for accessibility and should stay common in rest of the other examples in this article.
Animating display toggles
Typically, developers tend to use RAF or an animation library like Motion, but if your aim is achieving simple subtle transitions, CSS is totally capable of that.
With @starting-style rule, it’s now possible to animate elements that transition to or from display: none. This means that we don’t need JavaScript to take care of the transition in this case.
#product-filters {
display: none;
opacity: 1;
position: absolute;
transition:
display 200ms allow-discrete,
opacity 200ms ease-in-out;
@starting-style {
opacity: 0;
}
}
/* Closed state */
#product-filters[style*="display: none"] {
opacity: 0;
}
This setup works smoothly with your traditional markup and even with front-end technologies like Svelte, Vue, and React.
However, intense use of such interactions will cause rapid layout reflows on every getComputedStyle calculation, costing us performance.
The Framework way: Conditional Rendering
When it comes to toggling elements on and off with JavaScript frameworks like React and Svelte, instead of fiddling with CSS display, developers use conditional rendering to enjoy on the fly addition and removal of a component in the DOM.
export default function ProductFilters() {
const [isVisible, setIsVisible] = useState(true);
return (
<>
<button
type="button"
aria-expanded={isVisible}
aria-controls="product-filters"
onClick={() => setIsVisible((v) => !v)}>Toggle</button>
{isVisible && <div className="filter-panel">...</div>}
</>
);
}
This approach may make sense in simple use cases, but if such DOM operations are done very rapidly, they will negatively impact the performance of our app.
Removing a component from the DOM tree is a structural decision, that also wipes out its state, context, scroll position etc. If you want to preserve all these, it’s better to stick to a presentational way, which we are about to discuss next.
The “hidden” attribute way
Web browsers now support the hidden HTML property widely, covering over 96% of the users approximately. This attribute lets you hide an element without even touching CSS or DOM:
export default function ProductFilters() {
const [isHidden, setIsHidden] = useState(true);
return (
<>
<button onClick={() => setIsHidden((h) => !h)}>Toggle</button>
<div className="filter-panel" hidden={isHidden}>...</div>
</>
);
}
The semantic approach
Use the appropriate API to implement differnt display toggles. If you want to toggle a modal’s display, use the Dilaog API. If it’s a popup menu, use the Popover API. If it’s an accordion-like pattern, use the Disclosure API.
These APIs are accessibility-aware, which means you won’t necessarily need to set up ARIA attributes with them.
For the animation part, the @starting-style approach works well with these APIs too, which is a topic to cover in some future post.
Conclusion
We discussed presentational as well as structural toggling of elements in the frontend. The best mindset to make the right decision in such cases is to be aware of the elements you are handling, and then pick a suitable method.
I hope you enjoyed reading this piece. In the next update, I’ll be adding some working demos here with some practical use cases.
Written by Rahul C.
Founder and curator of W3Bits. Have questions, suggestions, or anything to say about this post? Reach out on X or send me an email.