Journal
Container Queries in Web Components | Max Böck
Max’s demo is really clever and features lots of interesting web component related techniques.
I came up with this demo of a book store. Each of the books is draggable and can be moved to one of three sections, with varying available space. Depending on where it is placed, different styles will be applied to the book.
Some of the techniques I found interesting included:
- starting with basic HTML for each book and its image, title, and author elements rather than an empty custom element, thereby providing a resilient baseline
- wrapping each book in a custom
book-elementtag (which the browser would simply treat like adivin the worst case scenario) - applying the
slotattribute to each of the nested elements, for exampleslot="title" - including a
templatewithid="book-element"at the top of the HTML. This centralises the optimal book markup, which makes for quicker, easier, and less disruptive maintenance. (Atemplateis parsed but not rendered by the browser. It is available solely to be referenced and used by JavaScript) - including slots within the
template, such as<slot name="title"> - putting a
styleblock within thetemplate. These styles target the book component only, and include container query driven responsiveness - targetting the
<book-element>wrapper element in CSS via the:hostselector, and applyingcontainto set it as a container query context - targetting a
slotin the component CSS using (for example)::slotted(img)
Thoughts
Firstly, in the basic HTML/CSS, I might ensure images are display: block and use div rather than span for a better baseline appearance should JavaScript fail.
Secondly, even though this tutorial is really nice, I still find myself asking: why use a Web Component to render a book rather than a server-side solution when the latter removes the JS dependency? Part of the reason is no doubt developer convenience—people want to build component libraries in JavaScript if that’s their language of choice. Also, it requires less backend set-up and leads to a more portable stack. And back-end tools for component-based architectures are generally less mature and feature-rich then those for the front-end.
One Web Component specific benefit is that Shadow DOM provides an encapsulation mechanism to style, script, and HTML markup. This encapsulation provides private scope that both prevents the content of the component from being affected by the external document, and keeps its CSS and JS from leaking out… which might be nice for avoiding the namespacing you’d otherwise have to do.
I have a feeling that Web Components might make sense for some components but be neither appropriate nor required for others. Therefore just because you use Web Components doesn’t mean that you suddenly need to feel the need to write or refactor every component that way. It’s worth bearing in mind that client-side JavaScript based functionality comes with a performance cost—the user needs to wait for it to download. So I feel there might be a need to exercise some restraint. I want to think about this a little more.
Other references
Ruthlessly eliminating layout shift on netlify.com, by Zach Leatherman
I love hearing about clever front-end solutions which combine technologies and achieve multiple goals. In Zach’s post we hear how Netlify’s website suffered from layout shift when conditionally rendering dismissible promo banners, and how he addressed this by rethinking the problem and shifting responsibilities around the stack.
Here’s my summary of the smart ideas covered in the post:
- decide on the appropriate server-rendered content… in this case showing rather than hiding the banner, making the most common use case faster to load
- have the banner “dismiss” button’s event handling script store the banner’s
hrefin the user’s localStorage as an identifier accessible on return visits - process lightweight but critical JavaScript logic early in the
<head>… in this case a check for this banner’s identifier existing in localStorage - under certain conditions – in this case when the banner was previously seen and dismissed – set a “state” class (
banner--hide) on the<html>element, leading to the component being hidden seamlessly by CSS - build the banner as a web component, the first layer of which being a custom element
<announcement-banner>and the second a JavaScript class to enhance it - delegate responsibility for presenting the banner’s “dismiss” button to the same script responsible for the component’s enhancements, meaning that a broken button won’t be presented if that script were to break.
So much to like in there!
Here are some further thoughts the article provoked.
Web components FTW
It feels like creating a component such as this one as a web component leads to a real convergence of benefits:
- tool-free, async loading of the component JS as an ES module
- fast, native element discovery (no need for a
document.querySelector) - enforces using a nice, idiomatic class providing encapsulation and high-performing native callbacks
- resilience and progressive enhancement by putting all your JS-dependent stuff into the JS class and having that enhance your basic custom element. If that JS breaks, you still have the basic element and won’t present any broken elements.
Even better, you end up with framework-independent, standards-based component that you could share with others for reuse elsewhere, just like Zach did.
Multiple banners
I could see there being a case where there are multiple banners during the same time period. I guess in that situation the localStorage banner value could be a stringified object rather than a simple, single-URL string.
Setting context on the root
It’s really handy to have a way to exert just-in-time control over the display of a server-rendered element in a way that avoids flashes of content… and adding a class to the <html> element offers that. In this approach, we run the small amount of JavaScript required to test a local condition (e.g. checking for a value in localStorage) really early. That lets us process our conditional logic before the element is rendered… although this also means that it’s not yet available in the DOM for direct manipulation. But adding a class to the HTML element means that we can pre-prepare CSS to use that class as a contextual selector for hiding the element.
We’re already familiar with the technique of placing classes on the root element from libraries like modernizr and some font-loading approaches, but this article serves as a reminder that we can employ it whenever we need it.
Handling the close button
Zach’s approach to handling the banner’s dismiss button was interesting. He makes sure that it’s not shown unless the web component’s JavaScript runs successfully which is great, but rather than inject it with JavaScript he includes it in the initial HTML but hidden with CSS, and his method of hiding is opacity.
We use opacity to toggle the close button so that it doesn’t reflow the component when it’s enabled via JavaScript.
I think what Zach’s saying is that the alternatives – inserting the button with JS, or toggling the hidden attribute or its CSS counterpart display:none – would affect geometry causing the browser to perform layout… whereas modifying opacity does not.
I love that level of diligence! Typically I prefer to delegate responsibility for inserting JS-dependent buttons to JavaScript because in comparison to including a button in the server-rendered HTML then hiding it, it feels more resilient and a more maintainable separation of concerns. However as always the best solution depends on the situation.
If I were going down Zach’s route I think I’d replace opacity with visibility since the latter hiding method removes the hidden element from the document which feels more accessible, while still avoiding triggering the reflow that display would.
Side-thoughts
In a server-side scripted application – one using Rails or PHP, for example – you could alternatively handle persisting state with cookies rather than localStorage… allowing you to test for the presence of the cookie on the server then handle conditional rendering of the banner on the server too, rather than needing classes which trigger hiding. I can see an argument for that. Thing is though, not everyone’s working in that environment. Zach has provided a standalone solution.
References
- Zach’s Herald of the dog web component
- CSS Triggers of reflow and repaint
- Minimising layout thrashing
One web component to rule them all? (on Filament Group, Inc.)
Scott Jehl has taken a refreshingly Progressive Enhancement -centric look at Web Components.
this pattern provides a nice hook for adding progressive enhancements to already-meaningful HTML contained in these custom elements, leaving them resilient in the case of of script loading failures and allowing the page to start rendering before the JS happens to run.
He goes further and creates a factory for creating Web Components which allows adding to a single element many small behavioural script enhancements that may or may not relate to each other.
There are also some great notes on polyfilling and the performance upgrade provided by lifecycle callbacks.
And Scott’s wc-experiments repo contains some interesting demos.
Changes at Basecamp
Basecamp—makers of project management, team communication and email software—have taken a controversial new stance against (amongst other things) political discussion at work, “paternalistic benefits” and 360 performance reviews.
These are difficult enough waters to navigate in life, but significantly more so at work. It's become too much. It's a major distraction. It saps our energy, and redirects our dialog towards dark places. It's not healthy, it hasn't served us well.
On the surface I can see the appeal of “keeping it simple” in the modern workplace where so many different things vie for our attention. However the move to “silence conversation” feels out of step with a moment when—as a society—we’re trying hard to correct institutional, longstanding inequalities. Perhaps the channels where political/societal discourse can happen need better demarcated and the “house rules” properly set. But especially after the year we’ve just had it also feels wrong to take a dictatorial approach toward staff.
It seems that many departing employees feel the same way.
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
preserveAspectRatioto 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 solidfillto 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
IntersectionObserverobject, 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
observeon your instance, passing in the element you want to watch. If you have multiple elements to watch, you could callobserverepeatedly in a loop through the relevantNodeList. - 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
MutationObserverobject, passing in a callback function. - The callback function will be called every time the DOM is changed.
- Call
observeon 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.