Skip to main content

Journal

Changing visual order with CSS

When considering using Flexbox or CSS Grid to change the visual order of elements, remember that “with great power comes great responsibility”.

Flexbox and CSS Grid have given us new powers to shuffle the visual order of elements on a web page such that it is different to the element order in the source code.

However many smart developers have rightly pointed out that although this is really convenient and can help in conquering gnarly design and responsive challenges, it’s often not a good idea. Rachel Andrew in particular has regularly offered the following advice:

Don’t fix source problems with Grid or Flexbox.

See Rachel’s talk Grid, content re-ordering and accessibility for more detail.

So, what’s the problem?

Essentially we cause people problems when we create a disconnect between tabbing order and visual order.

Let’s dig into the detail.

People browse web pages in different ways

We know that a web page’s visitors are likely to have a range of different abilities. While some experience the full extent of our visual design, others are visually-impaired or blind. Others still have motor impairments. The web was intended to be accessible and therefore web pages should cater appropriately to this range of user needs.

Relatedly we also know that people will use one or more of the following methods to move around a web page:

  • a thumb and fingers;
  • a mouse (or trackpad) and keyboard;
  • a keyboard alone.

There are probably other methods I’m forgetting, but those are the most common.

In order to cater to people who navigate a web page using a keyboard without a mouse (a.k.a. keyboard users), we must ensure that the page is tab-friendly. We want the page’s interactive elements to be navigable and focusable by pressing the Tab key. Those elements should be focusable one by one in the same order as they are defined in the HTML document.

Often, achieving tab-friendliness requires little or no additional development effort because the browser gives us so much of the necessary behaviour natively. Interactive HTML elements like anchors (links) and form controls are focusable by default. And for any custom interactions where we want to make a normally non-focusable element (such as a div) focusable (thus giving it a keyboard tab-stop) we can apply the tabindex attribute, setting its tabindex="0".

A simple, well-formed web page will natively offer a natural and predictable tabbing order.

With CSS Grid and Flexbox we now have new CSS options such as order, flex-direction: row-reverse and grid-auto-flow which let us reorder elements on the page. This can be really handy for situations where a design spec calls for Element 1 to appear above Element 2 on narrow viewports but to its right on wider viewports. Using these new CSS properties we might let the elements follow the source order on narrow viewports but change their order within a media query for wider viewports.

However this means we are now messing with the natural order of things. And if we move focusable elements around such that their visual order is different from their source order this creates a disconnect between tabbing order and visual order.

The disconnect is unlikely to be apparent to sighted people on a tablet/phone or on a desktop setup and navigating with their mouse, because they are not using tab to navigate. Blind users using a screen reader will be similarly unaffected because they don’t experience the visual order.

We might assume that all keyboard (only) users are also blind, using a screen reader, and not perceiving visual order. However keyboard users also include sighted people who have trouble operating a pointing device such as a mouse or trackball, or whose mouse is currently not working, or who simply prefer to use a keyboard where possible.

For the sighted person who navigates by keyboard our change of order would be confusing. When you use tab and expect that to follow convention and set focus on one element but it actually sets focus on another, you’ll likely assume that something is broken.

Rachel Andrew argues that creating this kind of disconnect is to be avoided. Léonie Watson suggests that in simple examples such a disconnect may only be mildly awkward, however in complex interfaces it could make things horribly unusable.

Is it ever OK to change visual order with CSS?

At work I recently reviewed a PR where the visual order of a Flexbox layout was being updated via order: -1 in a media query for wide viewports. Although it was a really elegant solution on paper, I advised caution in light of the pitfalls mentioned above.

However a colleague rightly pointed out that the elements being reordered were two divs. In this case and any others where the elements (and their children) are non-focusable and we’re not messing with the likes of navigation links or form elements, I think we are safe.

The Future

We’ve recently been given a few new CSS layout powers only to subsequently be advised not to use them.

How do we move forward?

Rachel Andrew argues that this problem needs addressed at a CSS level; that developers should be able to set the tab and reading order to follow the visual rather than source order under certain circumstances.

Until that becomes a CSS reality we should continue to be judicious in our usage to ensure we don’t break user expectations. We can also use tools like the Source Order Viewer in Chrome DevTools (DevTools > Elements > Accessibility) to check our work.

SVG Crop

Here’s a handy-looking tool for the SVG editing toolkit.

Remove blank space from around any SVG instantly.

(via @chriscoyier)

I’ve started reading Exhalation by Ted Chiang.

Swipey image grids (on cassie.codes)

A lovely post by Cassie Evans in which she demonstrates that SVG is not just for icons and illustrations. You might also reach for it to create a responsive, animated grid of images.

we have another grid at our disposal. SVG has its own internal coordinate system and it's responsive by design.

There are lots of interesting techniques in here such as:

  • layering an <image> on top of a shape such as a <rect>;
  • using preserveAspectRatio to make the image fully cover and fit the shape when their aspect ratios differ;
  • using <clipPath> as a custom-shaped window to an image (for example a <circle>);
  • giving the <clipPath>’s nested shape a solid fill to hide the <image> to which it’s applied;
  • animating a rectangular <clipPath> applied to an image to give the effect of the image “sliding into view”; and
  • using Greensock to make sequencing multiple animations easy and to ensure they work consistently across browsers.

However I also loved this simple piece of practical advice to help picture the SVG’s viewbox and plot shapes on top:

I usually get a pen and paper out at this point and doodle it out.

Observer APIs in a nutshell

I’ve played with the various HTML5 Observer APIs (IntersectionObserver, ResizeObserver and MutationObserver) a little over the last few years—for example using ResizeObserver in a container query solution for responsive grids. But in all honesty their roles, abilities and differences haven’t yet fully stuck in my brain. So I’ve put together a brief explainer for future reference.

Intersection Observer

Lets you watch for when an element of your choice intersects with a root element of your choice—typically the viewport—and then take action in response.

So you might watch for a div that’s way down the page entering the viewport as a result of the user scrolling, then act upon that by applying a class which animates that div’s opacity from 0 to 1 to make it fade in.

Here’s how it works:

  • Instantiate a new IntersectionObserver object, passing in firstly a callback function and secondly an options array which specifies your root element (usually the viewport, or a specific subsection of it).
  • Call observe on your instance, passing in the element you want to watch. If you have multiple elements to watch, you could call observe repeatedly in a loop through the relevant NodeList.
  • in the callback function add the stuff you want to happen in response to “intersecting” and “no longer intersecting” events.

Mutation Observer

Lets you watch for changes to the attributes or content of DOM elements then take action in response.

You might use this if you have code that you want to run if and when an element changes because of another script.

Here’s how it works:

  • Your typical starting point is that you already have one or more event listeners which modify the DOM in response to an event.
  • Instantiate a new MutationObserver object, passing in a callback function.
  • The callback function will be called every time the DOM is changed.
  • Call observe on your instance, passing in as first argument the element to watch and as second argument a config object specifying what type of changes you’re interested in, for example you might only care about changes to specific attributes.
  • your callback function provides an array of MutationRecord objects—one for each change that has just taken place—which you can loop through and act upon.

Resize Observer

Lets you watch for an element meeting a given size threshold then take action in response.

For example you might add a class of wide to a given container only when it is wider than 60em so that new styles are applied. This is a way of providing container query capability while we wait for that to land in CSS.

Or you might load additional, heavier-weight media in response to a certain width threshold because you feel you can assume a device type that indicates the user is on wifi. Adding functionality rather than applying styles is something we could not achieve with CSS alone.

Given that Container Query support is coming in CSS and that we can usually get by without it in the meantime, I don’t think it’s something I need so desperately that I’ll keep reaching for JavaScript to achieve it. However that’s not the only use for ResizeObserver.

It’s also worth remembering that it’s not all about width: ResizeObserver can also be used to detect and respond to changes to an element’s height. An example might be watching for changes to a chat window’s height—something that’s liable to happen as new messages appear—and then ensuring the focus stays at the bottom, on the latest message.

References

Steve Griffiths has some great video tutorials on these topics.

Design-ish systems (by Ethan Marcotte)

Here’s an interesting new article from Ethan Marcotte, in which he muses on better ways to think about Design Systems based on his recent experience.

Once you’ve identified the root causes, you’ll be in a far, far better place to choose the right things — and, more importantly, to create a system that finally supports your design.

Here’s what I took from it:

We can consider the difference between things, issues and broader goals.

It’s easy to focus on the things (design tokens, choice of design tool, programming language).

But things are just elements of the system; they don’t solve design systems.

Focusing too much or too early on the things leads to a tendency to patch problems.

Whereas discussing the current wider organisational issues we need to address leads to defining broader goals.

This in turn helps us choose and focus on the right things for the right reasons.

Diffchecker - Online diff tool

Diffchecker is a diff tool to compare text differences between two text files.

I had cause to use this free diff tool recently to compare two large minified CSS files and it did the trick better than any others I’ve tried. Thumbs up!

Building a resilient frontend using progressive enhancement (on GOV.UK)

GOV.UK’s guidance on developing using progressive enhancement is pretty great in all departments. It begins with this solid advice:

you should start by making your page work with just HTML, before adding anything else like Cascading Style Sheets (CSS) and JavaScript. This is because HTML is the most resilient layer. If the HTML fails there’s no web page. Should the CSS or JavaScript fail, the HTML will still render correctly.

I particularly like the section where they address the misconception that a resilient baseline is only required in places where the user has explicitly disabled JavaScript and therefore not worth worrying about.

You should not assume the reason for designing a service that works without CSS or JavaScript is because a user chooses to switch these off. There are many situations when extra layers can fail to load or are filtered.

As their subsequent list of scenarios illustrates, a user turning JavaScript off is probably the least likely of a range of reasons why extra layers on top of HTML can fail.

Relatedly, I’ve often found that Everyone has JavaScript, right? serves as a great go-to reference for these sorts of conversations around resilience.

I’ve started reading Recursion by Blake Crouch.

Encapsulated Eleventy/Nunjucks components with macros (by Trys Mudford)

Trys shows us how to use the Nunjucks macro to create encapsulated components. This works out less leaky and more predictable than an include preceded by variables assigned with set.

Trys’s solution allows us to render components like so:

{{ component('button', {
  text: 'Press me'
}) }}

{# Output #}
<button type="button">Press me</button>

Update, 8th Aug 2021: when I tried implementing this I found that it results in errors when attempting to render components anywhere other than on the base template where Trys recommended including the import line. The workaround—as Paul Salaets points out—is to include the import at the top of every page-level template (index.njk, archive.njk etc) that uses the component macro.