Showing and hiding parts of DOM is one of those things, that you do with javascript on everyday basis. It is so basic, that most javascript frameworks have it built in from the very beginning.
Toggling visibility usually means setting display: none
to hide the element and display: block
to show it again. Sometimes it is even made !important
, to make sure the style will be applied. But there is one problem – display: block
is not necessarily what we need. As Flexbox and CSS Grid got more popular and I used them more and more often, I started to have problems with my toggled elements, because all different flexbox properties stopped working. Block elements just don’t do anything with align-items
or flex-direction
values. So I needed to change my approach.
Dedicated classes for different display
values
One approach to the problem would be adding and removing classes with different display
values. Of course those classes could contain more properties, but keeping them that short would give us better compatibility with different scenarios. Everything, that is not display
should be applied in another way.
The code would look like this:
if (el.classList.contains('none')) {
el.classList.add('block');
el.classList.remove('none');
} else {
el.classList.add('none');
el.classList.remove('block');
}
This plays very well with atomic CSS. So if you’re using Tailwind or Bootstrap 4 that could be a way to go.
Inline style and saving default display
value before overriding
If our element starts with display
different from none
, we can read the value with Window.getComputedStyle()
before hiding and save that value in data-
attribute and re-apply when we want to make the element visible again. If our element begins in hidden state we can set the data-
attribute while generating the HTML. Alternativerly to data-
attribute we could use a custom property.
This approach has one advantage in comparison with atomic CSS: the javascript does not need to know anything about possible display
values or the context. If we get new layout possibilities in the future, we won’t need to change our toggle logic at all.
The code would look like this (data-display
contains value used to show the element):
let display = window.getComputedStyle(el).getPropertyValue('display');
if (display === 'none') {
el.style.setProperty('display', el.getAttribute('data-display'));
} else {
el.setAttribute('data-display', display);
el.style.setProperty('display', 'none');
}
or with a custom property:
let display = window.getComputedStyle(el).getPropertyValue('display');
if (display === 'none') {
el.style.setProperty('display', el.style.getPropertyValue('--display'));
} else {
el.style.setProperty('--display', display);
el.style.setProperty('display', 'none');
}
Show by default, hide on purpose
This is my favorite approach. It comes with one assumption – you want to show the users, what you have loaded and default value of display
for toggled elements is not none
(or is set to none
inline, because we can remove it easily with javascript in that case).
Whenever we want to hide an element we set the value of display
to none
with javascript and when we want to show the element we remove none
and cascade does the rest for us.
The code looks very similar to previous example:
let display = window.getComputedStyle(el).getPropertyValue('display');
if (display === 'none') {
el.style.removeProperty('display');
} else {
el.style.setProperty('display', 'none');
}
I consider it progressive enhancement friendly. Imagine a typical accordion or tabs interface – if javascript failed for any reason you would be more happy seeing everything at once than not seeing anything at all. The downside however is content shift when javascript kicks in. The shift can be minimized with different strategies, but can rarely be fully eliminated.
CSS only approach
It is possible to show and hide elements using checkboxes and selectors like :checked
or links and :target
. Even if this works it is not very flexible and not really accessible. I’ve seen accordions created with radio buttons and customizing them was hell. If possible, please, don’t do it. Just don’t.
HTML approach
There is a native details element with decent support, but I never really used it in production. As far as as I know it cannot be animated on opening/closing, which makes it unattractive for modern app-like interfaces. Constructing elements like tabs does not seem possible and I’m not sure about semantics of mobile menu built this way, but for most basic accordion it should be enough.