The Dangers of Logic in CSS

We can do a lot of logic in CSS. I’m thinking specifically of scenarios which take the form “if this, then that”.

There are media queries which will let us control the appearance of elements based on a variety of factors. Probably the most common is screen width, so we get things like “if the width is 1024px or higher, then show an extra element”.

There are pseudo selectors like :empty, :checked, :valid, :disabled which let us control appearance based on state, giving us, for example, “if the parent element has no children (:empty), then change its opacity”.

We can also do logic based on combinations of elements or selectors, like li + li, for example, “if the list item follows another list item, then give it a top border.”

I’m sure there are plenty more ways of applying logic but you get the idea. Each of these is equivalent to writing a conditional statement in a programming language – if this, then that.

The big question is, should we be doing this in CSS, and is there a point at which it becomes too difficult to manage in CSS and we need to defer to another technology, like JavaScript?

My Experience

I started thinking about this when I was designing a UI with a lot of logic going on. We had a busy table where we show some additional columns at wider screen resolutions. At smaller widths the user would be able to toggle between different groups of columns and save a preference. But then these additional columns are only offered if they contain data. So, we had logic around the screen width, user preference and presence of data, which, multiplied out, meant 8 permutations.

I initially tried to do it all with CSS. I set up media queries for the narrower and wider screen widths. Within each of these I then read a class for the user preference and then I used :empty to see if table cells contained any data. I managed it and it worked. Just.

However, I realised that it was really not scalable. Every time I added a new condition it doubled the amount of CSS I was having to write. If we wanted to add more logic in future it would create more and more work and be harder and harder to maintain.

By moving all the logic into JavaScript we can set simpler classes, one for each type of display we need rather than every permutation of logic. So now we just have 3 classes: 1) show the first set of columns, 2) show the second set or 3) show all columns.

We still have lots of branches in our JavaScript but the big difference is that it’s easy to read and follow. It’s also easy to edit or extend if we ever need to.

I think doing some logic with CSS is fine but as soon as you’re starting to layer it on and find yourself multiplying out the selectors you’re writing it’s probably time to move it over to JavaScript.

Clicking Off Things to Close Them

There’s a common design pattern these days where a bit of content is shown over the main screen. It might be a modal window or maybe a fly-in menu or sidebar. It’s also common, especially in native apps, that we can just click or tap in the space not used by these elements to close them. So, how can we do this?

I think there are 2 approaches, one which is pure JavaScript and another which involves an overlay element. Let’s look at both.

JavaScript Document Click Event Method

We can detect that a click is not on the content element using JavaScript. As all click events bubble up or propagate through the DOM tree they eventually reach the top document level. We can listen for a click on the document and then use the event target to check what was clicked. For this example let’s assume our content element has a class of ‘modal’, like <div class=”modal”></div>.

// JavaScript

function handleClick(event) {
  if (!event.target.closest('.modal')) {
    console.log('close');
  }
}

document.addEventListener('click', handleClick);

event.target gets the element that was clicked. This could be the modal element itself, another element inside it or another element outside of it. event.target.closest(‘.modal’) checks if the element clicked is the modal or is an antecedent of the modal element, an element within it. If the element clicked is not (!) in the modal we can close it.

It’s maybe worth noting that .closest() doesn’t work in IE11 if you need to support this but there is a polyfill available.

Using an Overlay Element

The other approach, and the one I tend to use, is to use an overlay element. This means adding an element that covers the whole screen area, slipped in between the modal and the main screen content. The idea is that this overlay will appear and disappear along with the modal and will pick up any click events which are not on the modal. It’s a bit like a safety net that will catch any stray clicks.

The CSS for the overlay would typically look something like this:

/* CSS */

.overlay {
  background-color:#000;
  bottom:0;
  left:0;
  opacity:.5;
  position:fixed;
  right:0;
  top:0;
  z-index:1;
}

.modal {
  /* styling */
  z-index:2;
}

The overlay has fixed display and goes to each edge so it fills the screen.

The overlay has z-index set to 1 so it’s over the main screen content, and the modal has it set to 2 so it’s over the overlay. These can increase as needed but the modal always needs to be higher.

I’ve given the overlay a background colour and 50% (.5) opacity so that it veils the main screen content underneath giving the modal a “lightbox” effect but these are not needed and our overlay can be effectively invisible.

You then just add a click event to the overlay which will close the modal and hide the overlay.

The reason I like this approach is that by covering the main screen we prevent any interactions with it. If the user tries to click on a button under the overlay their first click will just close the modal. We don’t need to worry about any other interactions – while the modal is showing it becomes the sole focus.

Extra tip. If you use a visible overlay it’s nicer not to show and hide it immediately but fade it in and out with a transition on the opacity, taking it between 0 and .5. This feels much smoother and less jerky.

Hiding UI Elements

At the start of March 2019 I ran a poll on Twitter to see how devs hide UI elements. Here’s the tweet and results.

50% CSS "display:none",
5% HTML "hidden" attribute,
27% JS framework state,
18% Something else

Admittedly, not a very scientific sample size but it was enough to confirm my suspicion that very few people use the “hidden” attribute. I should probably call it a property rather than an attribute as it’s a Boolean and acts in a similar way to “disabled” or “required” which we often use on form elements. And, by contrast, the most common method seems to be to use the CSS “display:none”. Probably no great surprise to anyone.

I think it’s interesting to think about what we’re really doing when we hide an element. Is the visibility of the element just for screen users or for all users? Do we still want the element available for non visual media? Or is the content going to be used by another part of the page in some way? Does it need to be in the DOM at all or are we just keeping it there because it’s easier?

If we use JavaScript, either native or a framework, we can insert and remove elements as needed based on logic. If it’s playing no part in our document why not just leave it out altogether? This keeps the DOM light and performant and easier to work with. If you’re working with something like automated testing you can check for an element’s existence rather than trying to figure out if it’s visible or not.

If we use CSS to hide an element and respect the separation of concerns principle then are we saying that hiding this element is a presentational choice rather than a semantic one? Maybe we are hiding it, maybe temporarily, rather than denying its existence?

In the case where we want an element to be hidden in one medium and shown in another, CSS feels like the right choice. Whilst “display:none” and “visibility:hidden” are valid, other techniques like “height:0”, “opacity:0” or “left:9999px” feel very unclear in their intentions and too focused on the visual user.

The HTML “hidden” property is a more semantic choice. It is stating that this element is not to be considered when reading the document, whether that be on screen or via assistive technologies. It is about more than visibility and is probably badly named. Maybe “ignore” would have been more fitting? It is more about the state of the element and closer in meaning to the input[type=hidden] that we used to see in the days before Ajax.

If you take away the JavaScript and CSS – maybe some network issues, maybe the HTML gets copied elsewhere – the element stays hidden. The point is that it’s meant to be hidden, it’s not styling.

The “hidden” property is also easy to work with and change in JavaScript. In fact, it’s easier than using a CSS class as you don’t have to write any CSS and you know it will work regardless.

<p class="disclaimer" hidden>This is a disclaimer</p>

<script>
  const disclaimer = document.querySelector('.disclaimer');
  function showDisclaimer() {
    // with a CSS class
    disclaimer.classList.add('show');
    // with the hidden property
    disclaimer.hidden = false;
  }
</script>

Think about why your content is hidden and how it may be reused and choose the most appropriate method of hiding it (or removing it).

To DOM or Not to DOM?

I’m trying to work out when it’s best to put attributes in the DOM and when it’s best to just leave them in JavaScript objects. This is going to be hard to explain so bear with me.

Let’s suppose we have a list of users on a page. Each user has a number of properties, not things we’d display like name, age and gender but hidden things like permissions, e.g. isAdmin, canComment, isManager, etc. These properties are going to be used in logic to determine how things are displayed on screen – maybe an extra bit of detail or an icon. Just as an example, if canComment is true then a speech bubble icon will appear.

The basic HTML might look something like this:

<ul>
  <li id="001" class="user">John</li>
  <li id="002" class="user">Paul</li>
  <li id="003" class="user">George</li>
</ul>

Behind this is a JavaScript object, like this:

var users = [
  {
    id: '001',
    name: 'John',
    canComment: true
  },
  {
    id: '002',
    name: 'Paul',
    canComment: false
  },
  {
    id: '003',
    name: 'George',
    canComment: false
  }
];

So, user John is allowed to comment and we want to show an icon. There are 2 approaches:

  1. Use the DOM

    We could add the icon inside each <li> and then add a class (class=”user can-edit”) or a data-attribute (data-can-edit) to the <li> and use CSS to display it as appropriate, e.g. .user[data-can-edit] .icon { display: inline-block }.

  2. Use JavaScript

    Or, we could write a JavaScript function to filter the objects to just those with canComment set to true, get their ids, and then loop through the list items adding the icon HTML when the ids match the filtered list.

The first approach puts the property into the DOM, puts the icon into the DOM and then uses CSS to show/hide it. It feels easy. The icon is always sat there waiting, like an unchecked checkbox, only shown when needed (checked). There is still logic but it’s done up front. The DOM is actually human readable and it’s easy to see which users can and can’t edit for debugging purposes. I see this approach used a lot, including the hidden DOM elements, usually because of the way templates or components are used. It’s easier to include it and pass a property to toggle whether or not it displays. It also means that if we want to change the property during the lifecycle of the page, e.g. take away John’s commenting permission, it’s minimal change to the DOM.

The second approach only touches the DOM to insert the icon where needed. It does all the logic in JavaScript and the output in the DOM reflects what the user actually sees on screen. There are no hidden elements. This has advantages when it’s scaled up, in that it keeps the DOM lighter and there’s no waste. The JavaScript is incredibly fast in modern browsers so the functions won’t slow anything down, especially if using .filter(), map(), etc. The trick here is not to loop through and add the icons one at a time but to run through building the whole list and just doing the one insert or replace operation. This makes the DOM less readable as all the canComment data is in JavaScript.

So, is it better to use the DOM for data or keep it in JavaScript?

 

Hacking Borders

Have you ever been in a situation where your borders aren’t rendering as expected? Hopefully not.

I’ve hit this issue recently using PhantomJS to turn HTML content into a PDF. It generally works pretty well but when you get into tables with collapsed borders and use rowspan and colspan things start getting a bit hairy and some of your borders just don’t get rendered. Sad face.

No amount of messing with border-collapse, display or border properties seems to have any affect. So, what can we do? Time to get dirty and hack our way out of it. Surgical gloves on.

We can’t change the borders so we create pseudo borders. We use CSS pseudo elements to create lines within the cells. Here’s the CSS for a top-border:

.hacked-border-top {
  position:relative;
}

.hacked-border-top:before {
  background-color:currentColor; /* or whatever */
  height:1px;
  left:0;
  position:absolute;
  top:-1px;
  width:100%;
}

The pseudo border is absolutely positioned within the cell, thickness (height) 1px, full width. The top:-1px moves it outside the box model to where the border would be.

Change the height, width and bottom, left, right and top values as need be for the different sides.

Please only use this as a last resort when all else fails.

In case of emergency break glass