Categories CSS javascript

Different approaches to show/hide toggles

Post date November 10, 2020

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *