Quantity queries using has() selector
Here’s a handy little tool for generating CSS with :has() selectors in order to do quantity queries.
Here’s a handy little tool for generating CSS with :has() selectors in order to do quantity queries.
A few years back, Craig wrote a great piece called Fast Software, the Best Software:
Speed in software is probably the most valuable, least valued asset. To me, speedy software is the difference between an application smoothly integrating into your life, and one called upon with great reluctance.
Nelson Elhage said much the same thing in his reflections on software performance:
I’ve really come to appreciate that performance isn’t just some property of a tool independent from its functionality or its feature set. Performance — in particular, being notably fast — is a feature in and of its own right, which fundamentally alters how a tool is used and perceived.
Or, as Robin put it:
I don’t think a website can be good until it’s fast.
Those sentiments underpin The Session. Speed is as much a priority as usability, accessibility, privacy, and security.
I’m fortunate in that the site doesn’t have an underlying business model at odds with these priorities. I’m under no pressure to add third-party code that would track users and slow down the website.
When it comes to making fast websites, most of the obstacles are put in place by front-end development, mostly JavaScript. I’ve been pretty ruthless in my pursuit of speed on The Session, removing as much JavaScript as possible. On the bigger pages, the bottleneck now is DOM size rather than parsing and excuting JavaScript. As bottlenecks go, it’s not the worst.
But even with all my core web vitals looking good, I still have an issue that can’t be solved with front-end optimisations. Time to first byte (or TTFB if you’d rather use an initialism that takes just as long to say as the words it’s replacing).
When it comes to reducing the time to first byte, there are plenty of factors that are out of my control. But in the case of The Session, something I do have control over is the server set-up, specifically the database.
Now I could probably solve a lot of my speed issues by throwing money at the problem. If I got a bigger better server with more RAM and CPUs, I’m pretty sure it would improve the time to first byte. But my wallet wouldn’t thank me.
(It’s still worth acknowledging that this is a perfectly valid approach when it comes to back-end optimisation that isn’t available on the front end; you can’t buy all your users new devices.)
So I’ve been spending some time really getting to grips with the MySQL database that underpins The Session. It was already normalised and indexed to the hilt. But perhaps there were server settings that could be tweaked.
This is where I have to give a shout-out to Releem, a service that is exactly what I needed. It monitors your database and then over time suggests configuration tweaks, explaining each one along the way. It’s a seriously good service that feels as empowering as it is useful.
I wish I could afford to use Releem on an ongoing basis, but luckily there’s a free trial period that I could avail of.
Thanks to Releem, I was also able to see which specific queries were taking the longest. There was one in particular that had always bothered me…
If you’re a member of The Session, then you can see any activity related to something you submitted in the past. Say, for example, that you added a tune or an event to the site a while back. If someone else comments on that, or bookmarks it, then that shows up in your “notifications” feed.
That’s all well and good but under the hood it was relying on a fairly convuluted database query to a very large table (a table that’s effectively a log of all user actions). I tried all sorts of query optimisations but there always seemed to be some combination of circumstances where the request would take ages.
For a while I even removed the notifications functionality from the site, hoping it wouldn’t be missed. But a couple of people wrote to ask where it had gone so I figured I ought to reinstate it.
After exhausting all the technical improvements, I took a step back and thought about the purpose of this particular feature. That’s when I realised that I had been thinking about the database query too literally.
The results are ordered in reverse chronological order, which makes sense. They’re also chunked into groups of ten, which also makes sense. But I had allowed for the possibility that you could navigate through your notifications back to the very start of your time on the site.
But that’s not really how we think of notifications in other settings. What would happen if I were to limit your notifications only to activity in, say, the last month?
Boom! Instant performance improvement by orders of magnitude.
I guess there’s a lesson there about switching off the over-analytical side of my brain and focusing on actual user needs.
Anyway, thanks to the time I’ve spent honing the database settings and optimising the longest queries, I’ve reduced the latency by quite a bit. I’m hoping that will result in an improvement to the time to first byte.
Time—and monitoring tools—will tell.
There’s really good browser support for display-mode media queries and this article does a really good job of running through some of the use cases for your progressive web app.
Logical properties, container queries, :has, :is, :where, min(), max(), clamp(), nesting, cascade layers, subgrid, and more.
Another terrific interactive tutorial from Ahmad, this time on container queries.
Michelle has written a detailed practical guide to container queries here.
Container queries can’t be used in the sizes attribute for responsive images. Here, Jason breaks down why that is (spoiler: it’s the lookahead pre-parser) and segues into a truly long term solution: a “magical” image format.
If you’ve ever thought it felt weird to put media conditions inside the HTML for responsive images, this will resonate.
Instead of thinking about responsive design in terms of media queries, I like to think of responsive design in these categories.
- Responsive to the content
- Responsive to the viewport
- Responsive to the container
- Responsive to the user preferences
I feel like we need a name for this era, when CSS started getting real good.
I think this is what I’ve been calling declarative design.
The algorithm I’m going after is pretty simple: If the grid of items has an odd number of items, then make the first item full-width. But CSS can’t do logic… right? Well… hold my proverbial beer.
Some thoughts on CSS, media queries, and fluid type prompted by Utopia:
We say CSS is “declarative”, but the more and more I write breakpoints to accommodate all the different ways a design can change across the viewport spectrum, the more I feel like I’m writing imperative code. At what quantity does a set of declarative rules begin to look like imperative instructions?
In contrast, one of the principles of Utopia is to be declarative and “describe what is to be done rather than command how to do it”. This approach declares a set of rules such that you could pick any viewport width and, using a formula, derive what the type size and spacing would be at that size.
It’s said that the best way to learn about something is to teach it. I certainly found that to be true when I was writing the web.dev course on responsive design.
I felt fairly confident about some of the topics, but I felt somewhat out of my depth when it came to some of the newer modern additions to browsers. The last few modules in particular were unexplored areas for me, with topics like screen configurations and media features. I learned a lot about those topics by writing about them.
Best of all, I got to put my new-found knowledge to use! Here’s how…
The Session is a progressive web app. If you add it to the home screen of your mobile device, then when you launch the site by tapping on its icon, it behaves just like a native app.
In the web app manifest file for The Session, the display-mode property is set to “standalone.” That means it will launch without any browser chrome: no address bar and no back button. It’s up to me to provide the functionality that the browser usually takes care of.
So I added a back button in the navigation interface. It only appears on small screens.
Do you see the assumption I made?
I figured that the back button was most necessary in the situation where the site had been added to the home screen. That only happens on mobile devices, right?
Nope. If you’re using Chrome or Edge on a desktop device, you will be actively encourged to “install” The Session. If you do that, then just as on mobile, the site will behave like a standalone native app and launch without any browser chrome.
So desktop users who install the progressive web app don’t get any back button (because in my CSS I declare that the back button in the interface should only appear on small screens).
I was alerted to this issue on The Session:
It downloaded for me but there’s a bug, Jeremy - there doesn’t seem to be a way to go back.
Luckily, this happened as I was writing the module on media features. I knew exactly how to solve this problem because now I knew about the existence of the display-mode media feature. It allows you to write media queries that match the possible values of display-mode in a web app manifest:
.goback {
display: none;
}
@media (display-mode: standalone) {
.goback {
display: inline;
}
}
Now the back button shows up if you “install” The Session, regardless of whether that’s on mobile or desktop.
Previously I made the mistake of inferring whether or not to show the back button based on screen size. But the display-mode media feature allowed me to test the actual condition I cared about: is this user navigating in standalone mode?
If I hadn’t been writing about media features, I don’t think I would’ve been able to solve the problem. It’s a really good feeling when you’ve just learned something new, and then you immediately find exactly the right use case for it!
The final five are here! The course on responsive design I wrote for web.dev is now complete, just in time for Christmas. The five new modules are:
These five felt quite “big picture”, and often quite future-facing. I certainly learned a lot researching proposals for potential media features and foldable screens. That felt like a fitting way to close out the course, bookending it nicely with the history of responsive design in the introduction.
And with that, the full course is now online. Go forth and learn responsive design!
I knew that custom properties don’t work in media queries but I had no idea that there was such a thing as custom media queries, which effectively do the same thing.
But this is not implemented in any browser. Boo! This would be so useful! If browser makers can overcame the technical hurdles with container queries, I’m sure they can deliver custom media queries.
I think Bruce is onto something here:
It seems to me that browsers could do more to protect their users. Browsers are, after all, user agents that protect the visitor from pop-ups, malicious sites, autoplaying videos and other denizens of the underworld. They should also protect users against nausea and migraines, regardless of whether the developer thought to (or had the tools available to).
So, I propose that browsers should never respect
scroll-behavior: smooth;if a user prefers reduced motion, regardless of whether a developer has set the media query.
The point of this post is to show how nicely container queries can play with web components, but I want to also point out how nice the design of the web component is here: instead of just using an empty custom element, Max uses progressive enhancement to elevate the markup within the custom element.
Huh. I don’t think I ever thought about nesting media queries …and yet I’m pleasantly surprised that it works!
Removing
mediasupport from HTML video was a mistake.
Damn right! It was basically Hixie throwing a strop, trying to sabotage responsive images. Considering how hard it is usually to remove a shipped feature from browsers, it’s bizarre that a good working feature was pulled out of production.
I like the way that Simon is liberating his data from silos and making it work for him.
Container queries are like buses: you’re waiting for ages and then two come along at once.
This switch() syntax looks interesting.