Front End Web Development
AR, VR, and a Model for 3D in HTML
Tucked down somewhere in the Safari Technology Preview 161 release notes is a seemingly innocous line about support for a new HTML element and attribute:
Added support for <model src> and honor <source type> attributes (257518@main)
Anytime I see mention of some element I don’t recognize, my mind goes straight to Huh! New to me, but probably old news for everyone else. It’s poor posture, I know, as it could just as easily be:
- Hmm, looks like some propriatary experiment.
- Wow, a truly new thing!
Truth is, it’s sorta all three.
It’s an evolving conceptAs in, the first somewhat official-sounding thing I found on <model> wasn’t in the W3C spec but in WebKit’s repo for explainers. All that’s in the README is a giant note from 2021 that “The <model> element has moved to the Immersive Web CG.” I was about to hop over but my eye caught the HistoryAndEvolution.md file which has a nice rundown of early context on the <model> concept:
The <model> element was born out of a desire to take the next step and improve the experience of Safari’s integration with iOS’s AR Quick Look feature.
I had to look at Apple’s splash page for AR Quick Look. You know the new feature that some stores have where you can transpose a 3D rendering of a product in your own home using your phone camera? That’s the sort of stuff we’re talking about, and Apple links up a nice case study from the Metropolitan Museum of Art.
As I understand it from this limited context:
- Drop a <model> element in the document.
- Add an external source file, e.g. <model src="assets/example.usdz">.
That’s the team looking make Virtual Reality (VR) and Augmented Reality (AR) part of the web. Apple linked up their repo, so I made the jump and went straight to the explainer. This isn’t the spec or anything, but the original proposal. A much better definition of the element!
HTML allows the display of many media types through elements such as <img>, <picture>, or <video>, but it does not provide a declarative manner to directly display 3D content. Embedding 3D content within a page is comparatively cumbersome and relies on scripting the <canvas> element. We believe it is time to put 3D models on equal footing with other, already supported, media types.
[…]
The HTML <model> element aims to allow a website to embed interactive 3D models as conveniently as any other visual media. Models are expected to be created by 3D authoring tools or generated dynamically, but served as a standalone resource by the server.
The basic example pulls this together. It really does feel like the <video> or <picture> elements:
<model style="width: 400px; height: 300px"> <source src="assets/example.usdz" type="model/vnd.usdz+zip"> <source src="assets/example.glb" type="model/gltf-binary"> </model>.usdz? .glb? Not the type of files that typically cross my desk. Guess I’ll need to brush up on those and any other file types that <model> might support. Again, all of this is merely the original proposal.
The draft proposal isn’t stubbed out quite yetBut it does provide a nice outline of where things could possibly go:
- Adding a model to a document
- Enabling interactivity
- Supporting multiple formats
- Providing fallback content
- Making it accessible
There’s a lot to figure out. Most of what’s there are documented issues that need addressing. It does, however, shed more light on <model> like proposed attributes that make it feel even more like <video> such as autoplay, controls, loop, muted, poster, etc.
It goes back even furtherThe very earliest mention of 3D modeling I found was Keith Clark’s 2018 post in which he prototypes a custom element called <x-model>. He describes it as “a placeholder that provides access to the DOM and CSSOM” where the loading and rendering is done in three.js.
Keith’s idea is followed by the <model-viewer> component Joe Medley shared in 2020 (and a subsequent update to it). There’s even a homepage for it and it’s fun to drag Neil Armstrong around in space.
It’s possibly just an experiment?I mean, the draft spec hasn’t been fleshed out. Apple seems willing to play ball thanks to the Safari TP 161 announcement. That makes total sense given how bullish Apple is on AR as a whole. (Apple Glasses, anyone?)
Google seems to have its foot in the door, albeit on the Web Components side of things. It’s easy to see how there may be a conflict of interest between what Apple and Google want from AR on the web.
These are all just my notes from trying to grok everything. There’s gotta be a lot more nuance to it than what little I know about it so far. I’m sure someone smarter can tie neater bow around <model> in the comments. 😉
And while we’re talking Safari Technology Preview, 162 just released the other day and it enables CSS nesting and the CSS relative color syntax.
AR, VR, and a Model for 3D in HTML originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Animating CSS Grid (How To + Examples)
I’m pleased to shine a light on the fact that the CSS grid-template-rows and grid-template-columns properties are now animatable in all major web browsers! Well, CSS Grid has technically supported animations for a long time, as it’s baked right into the CSS Grid Layout Module Level 1 spec.
But animating these grid properties only recently gained supported by all three major browsers. Shall we take a look at a few examples to get the creative juices flowing?
Table of contents- Table of contents
- Example 1: Expanding sidebar
- Example 2: Expanding Panels
- Example 3: Adding Rows and Columns
- A few more examples
First of all, this is what we’re talking about:
CodePen Embed FallbackA simple two-column grid. Now, before, you might not have built this using CSS Grid because animations and transitions weren’t supported, but what if you wanted the left column — perhaps a sidebar navigation — to expand on hover? Well, now that’s possible.
I know what you’re thinking: “Animating a CSS property? Easy peasy, I’ve been doing it for years!” Me too. However, I ran into an interesting snag while experimenting with a particular use case.
So, we want to transition the grid itself (specifically grid-template-columns, which is set on the .grid class in the example). But the left column (.left) is the selector that requires the :hover pseudo-class. While JavaScript can solve this conundrum easily — thanks, but no thanks — we can accomplish it with CSS alone.
Let’s walk through the whole thing, starting with the HTML. Pretty standard stuff really… a grid with two columns.
<div class="grid"> <div class="left"></div> <div class="right"></div> </div>Putting the cosmetic CSS aside, you’ll first need to set display: grid on the parent container (.grid).
.grid { display: grid; }Next, we can define and size the two columns using the grid-template-columns property. We’ll make the left column super narrow, and later increase its width on hover. The right column takes up the rest of the remaining space, thanks to the auto keyword.
.grid { display: grid; grid-template-columns: 48px auto; }We know we’re going to animate this thing, so let’s go ahead and throw a transition in there while we’re at it so the change between states is smooth and noticeable.
.grid { display: grid; grid-template-columns: 48px auto; transition: 300ms; /* Change as needed */ }That’s it for the .grid! All that’s left is to apply the hover state. Specifically, we’re going to override the grid-template-columns property so that the left column takes up a greater amount of space on hover.
This alone isn’t all that interesting, although it’s awesome that animations and transitions are supported now in CSS Grid. What’s more interesting is that we can use the relatively new :has() pseudo-class to style the parent container (.grid) while the child (.left) is hovered.
.grid:has(.left:hover) { /* Hover styles */ }In plain English this is saying, “Do something to the .grid container if it contains an element named .left inside of it that is in a hover state.” That’s why :has() is often referred to as a “parent” selector. We can finally select a parent based on the children it contains — no JavaScript required!
So, let’s increase the width of the .left column to 30% when it is hovered. The .right column will continue to take up all the leftover space:
.grid { display: grid; transition: 300ms; grid-template-columns: 48px auto; } .grid:has(.left:hover) { grid-template-columns: 30% auto; }We could use CSS variables as well, which may or may not look cleaner depending on your personal preferences (or you might be using CSS variables in your project anyway):
.grid { display: grid; transition: 300ms; grid-template-columns: var(--left, 48px) auto; } .grid:has(.left:hover) { --left: 30%; }I love that CSS grids can be animated now, but the fact that we can build this particular example with just nine lines of CSS is even more astounding.
Here’s another example by Olivia Ng — similar concept, but with content (click on the nav icon):
CodePen Embed Fallback Example 2: Expanding Panels CodePen Embed FallbackThis example transitions the grid container (the column widths) but also the individual columns (their background colors). It’s ideal for providing more content on hover.
It’s worth remembering that the repeat() function sometimes produces buggy transitions, which is why I set the width of each column individually (i.e. grid-template-columns: 1fr 1fr 1fr).
Example 3: Adding Rows and Columns CodePen Embed FallbackThis example animatedly “adds” a column to the grid. However — you guessed it — this scenario has a pitfall too. The requirement is that the “new” column mustn’t be hidden (i.e. set to display: none), and CSS Grid must acknowledge its existence while setting its width to 0fr.
So, for a three-column grid — grid-template-columns: 1fr 1fr 0fr (yes, the unit must be declared even though the value is 0!) transitions into grid-template-columns: 1fr 1fr 1fr correctly, but grid-template-columns: 1fr 1fr doesn’t. In hindsight, this actually makes perfect sense considering what we know about how transitions work.
Here’s another example by Michelle Barker — same concept, but with an extra column and lot more pizzazz. Make sure to run this one in full-screen mode because it’s actually responsive (no trickery, just good design!).
CodePen Embed Fallback A few more examplesBecause why not?
This “Animated Mondrian” is the original proof of concept for animated CSS grids by Chrome DevRel. The grid-row‘s and grid-column‘s utilize the span keyword to create the layout you see before you, and then the grid-template-row’s and grid-template-column‘s are animated using a CSS animation. It’s nowhere near as complex as it looks!
CodePen Embed FallbackSame concept, but with more of that Michelle Barker pizzazz. Could make a nice loading spinner?
CodePen Embed FallbackWrapping up with a bit of nostalgia (showing my age here), the not-very-griddy animated CSS grid by Andrew Harvard. Again — same concept — it’s just that you can’t see the other grid items. But don’t worry, they’re there.
CodePen Embed FallbackAnimating CSS Grid (How To + Examples) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Getting Started With SvelteKit
SvelteKit is the latest of what I’d call next-gen application frameworks. It, of course, scaffolds an application for you, with the file-based routing, deployment, and server-side rendering that Next has done forever. But SvelteKit also supports nested layouts, server mutations that sync up the data on your page, and some other niceties we’ll get into.
This post is meant to be a high-level introduction to hopefully build some excitement for anyone who’s never used SvelteKit. It’ll be a relaxed tour. If you like what you see, the full docs are here.
In some ways this is a challenging post to write. SvelteKit is an application framework. It exists to help you build… well, applications. That makes it hard to demo. It’s not feasible to build an entire application in a blog post. So instead, we’ll use our imaginations a bit. We’ll build the skeleton of an application, have some empty UI placeholders, and hard-coded static data. The goal isn’t to build an actual application, but instead to show you how SvelteKit’s moving pieces work so you can build an application of your own.
To that end, we’ll build the tried and true To-Do application as an example. But don’t worry, this will be much, much more about seeing how SvelteKit works than creating yet another To-Do app.
The code for everything in this post is available at GitHub. This project is also deployed on Vercel for a live demo.
Creating your projectSpinning up a new SvelteKit project is simple enough. Run npm create svelte@latest your-app-name in the terminal and answer the question prompts. Be sure to pick “Skeleton Project” but otherwise make whatever selections you want for TypeScript, ESLint, etc.
Once the project is created, run npm i and npm run dev and a dev server should start running. Fire up localhost:5173 in the browser and you’ll get the placeholder page for the skeleton app.
Basic routingNotice the routes folder under src. That holds code for all of our routes. There’s already a +page.svelte file in there with content for the root / route. No matter where in the file hierarchy you are, the actual page for that path always has the name +page.svelte. With that in mind, let’s create pages for /list, /details, /admin/user-settings and admin/paid-status, and also add some text placeholders for each page.
Your file layout should look something like this:
You should be able to navigate around by changing URL paths in the browser address bar.
LayoutsWe’ll want navigation links in our app, but we certainly don’t want to copy the markup for them on each page we create. So, let’s create a +layout.svelte file in the root of our routes folder, which SvelteKit will treat as a global template for all pages. Let’s and add some content to it:
<nav> <ul> <li> <a href="/">Home</a> </li> <li> <a href="/list">To-Do list</a> </li> <li> <a href="/admin/paid-status">Account status</a> </li> <li> <a href="/admin/user-settings">User settings</a> </li> </ul> </nav> <slot /> <style> nav { background-color: beige; } nav ul { display: flex; } li { list-style: none; margin: 15px; } a { text-decoration: none; color: black; } </style>Some rudimentary navigation with some basic styles. Of particular importance is the <slot /> tag. This is not the slot you use with web components and shadow DOM, but rather a Svelte feature indicating where to put our content. When a page renders, the page content will slide in where the slot is.
And now we have some navigation! We won’t win any design competitions, but we’re not trying to.
Nested layoutsWhat if we wanted all our admin pages to inherit the normal layout we just built but also share some things common to all admin pages (but only admin pages)? No problem, we add another +layout.svelte file in our root admin directory, which will be inherited by everything underneath it. Let’s do that and add this content:
<div>This is an admin page</div> <slot /> <style> div { padding: 15px; margin: 10px 0; background-color: red; color: white; } </style>We add a red banner indicating this is an admin page and then, like before, a <slot /> denoting where we want our page content to go.
Our root layout from before renders. Inside of the root layout is a <slot /> tag. The nested layout’s content goes into the root layout’s <slot />. And finally, the nested layout defines its own <slot />, into which the page content renders.
If you navigate to the admin pages, you should see the new red banner:
Defining our dataOK, let’s render some actual data — or at least, see how we can render some actual data. There’s a hundred ways to create and connect to a database. This post is about SvelteKit though, not managing DynamoDB, so we’ll “load” some static data instead. But, we’ll use all the same machinery to read and update it that you’d use for real data. For a real web app, swap out the functions returning static data with functions connecting and querying to whatever database you happen to use.
Let’s create a dirt-simple module in lib/data/todoData.ts that returns some static data along with artificial delays to simulate real queries. You’ll see this lib folder imported elsewhere via $lib. This is a SvelteKit feature for that particular folder, and you can even add your own aliases.
let todos = [ { id: 1, title: "Write SvelteKit intro blog post", assigned: "Adam", tags: [1] }, { id: 2, title: "Write SvelteKit advanced data loading blog post", assigned: "Adam", tags: [1] }, { id: 3, title: "Prepare RenderATL talk", assigned: "Adam", tags: [2] }, { id: 4, title: "Fix all SvelteKit bugs", assigned: "Rich", tags: [3] }, { id: 5, title: "Edit Adam's blog posts", assigned: "Geoff", tags: [4] }, ]; let tags = [ { id: 1, name: "SvelteKit Content", color: "ded" }, { id: 2, name: "Conferences", color: "purple" }, { id: 3, name: "SvelteKit Development", color: "pink" }, { id: 4, name: "CSS-Tricks Admin", color: "blue" }, ]; export const wait = async amount => new Promise(res => setTimeout(res, amount ?? 100)); export async function getTodos() { await wait(); return todos; } export async function getTags() { await wait(); return tags.reduce((lookup, tag) => { lookup[tag.id] = tag; return lookup; }, {}); } export async function getTodo(id) { return todos.find(t => t.id == id); }A function to return a flat array of our to-do items, a lookup of our tags, and a function to fetch a single to-do (we’ll use that last one in our Details page).
Loading our dataHow do we get that data into our Svelte pages? There’s a number of ways, but for now, let’s create a +page.server.js file in our list folder, and put this content in it:
import { getTodos, getTags } from "$lib/data/todoData"; export function load() { const todos = getTodos(); const tags = getTags(); return { todos, tags, }; }We’ve defined a load() function that pulls in the data needed for the page. Notice that we are not await-ing calls to our getTodos and getTags async functions. Doing so would create a data loading waterfall as we wait for our to-do items to come in before loading our tags. Instead, we return the raw promises from load, and SvelteKit does the necessary work to await them.
So, how do we access this data from our page component? SvelteKit provides a data prop for our component with data on it. We’ll access our to-do items and tags from it using a reactive assignment.
Our List page component now looks like this.
<script> export let data; $: ({ todo, tags } = data); </script> <table cellspacing="10" cellpadding="10"> <thead> <tr> <th>Task</th> <th>Tags</th> <th>Assigned</th> </tr> </thead> <tbody> {#each todos as t} <tr> <td>{t.title}</td> <td>{t.tags.map((id) => tags[id].name).join(', ')}</td> <td>{t.assigned}</td> </tr> {/each} </tbody> </table> <style> th { text-align: left; } </style>And this should render our to-do items!
Layout groupsBefore we move on to the Details page and mutate data, let’s take a peek at a really neat SvelteKit feature: layout groups. We’ve already seen nested layouts for all admin pages, but what if we wanted to share a layout between arbitrary pages at the same level of our file system? In particular, what if we wanted to share a layout between only our List page and our Details page? We already have a global layout at that level. Instead, we can create a new directory, but with a name that’s in parenthesis, like this:
We now have a layout group that covers our List and Details pages. I named it (todo-management) but you can name it anything you like. To be clear, this name will not affect the URLs of the pages inside of the layout group. The URLs will remain the same; layout groups allow you to add shared layouts to pages without them all comprising the entirety of a directory in routes.
We could add a +layout.svelte file and some silly <div> banner saying, “Hey we’re managing to-dos”. But let’s do something more interesting. Layouts can define load() functions in order to provide data for all routes underneath them. Let’s use this functionality to load our tags — since we’ll be using our tags in our details page — in addition to the list page we already have.
In reality, forcing a layout group just to provide a single piece of data is almost certainly not worth it; it’s better to duplicate that data in the load() function for each page. But for this post, it’ll provide the excuse we need to see a new SvelteKit feature!
First, let’s go into our list page’s +page.server.js file and remove the tags from it.
import { getTodos, getTags } from "$lib/data/todoData"; export function load() { const todos = getTodos(); return { todos, }; }Our List page should now produce an error since there is no tags object. Let’s fix this by adding a +layout.server.js file in our layout group, then define a load() function that loads our tags.
import { getTags } from "$lib/data/todoData"; export function load() { const tags = getTags(); return { tags, }; }And, just like that, our List page is rendering again!
We’re loading data from multiple locationsLet’s put a fine point on what’s happening here:
- We defined a load() function for our layout group, which we put in +layout.server.js.
- This provides data for all of the pages the layout serves — which in this case means our List and Details pages.
- Our List page also defines a load() function that goes in its +page.server.js file.
- SvelteKit does the grunt work of taking the results of these data sources, merging them together, and making both available in data.
We’ll use our Details page to edit a to-do item. First, let’s add a column to the table in our List page that links to the Details page with the to-do item’s ID in the query string.
<td><a href="/details?id={t.id}">Edit</a></td>Now let’s build out our Details page. First, we’ll add a loader to grab the to-do item we’re editing. Create a +page.server.js in /details, with this content:
import { getTodo, updateTodo, wait } from "$lib/data/todoData"; export function load({ url }) { const id = url.searchParams.get("id"); console.log(id); const todo = getTodo(id); return { todo, }; }Our loader comes with a url property from which we can pull query string values. This makes it easy to look up the to-do item we’re editing. Let’s render that to-do, along with functionality to edit it.
SvelteKit has wonderful built-in mutation capabilities, so long as you use forms. Remember forms? Here’s our Details page. I’ve elided the styles for brevity.
<script> import { enhance } from "$app/forms"; export let data; $: ({ todo, tags } = data); $: currentTags = todo.tags.map(id => tags[id]); </script> <form use:enhance method="post" action="?/editTodo"> <input name="id" type="hidden" value="{todo.id}" /> <input name="title" value="{todo.title}" /> <div> {#each currentTags as tag} <span style="{`color:" ${tag.color};`}>{tag.name}</span> {/each} </div> <button>Save</button> </form>We’re grabbing the tags as before from our layout group’s loader and the to-do item from our page’s loader. We’re grabbing the actual tag objects from the to-do’s list of tag IDs and then rendering everything. We create a form with a hidden input for the ID and a real input for the title. We display the tags and then provide a button to submit the form.
If you noticed the use:enhance, that simply tells SvelteKit to use progressive enhancement and Ajax to submit our form. You’ll likely always use that.
How do we save our edits?Notice the action="?/editTodo" attribute on the form itself? This tells us where we want to submit our edited data. For our case, we want to submit to an editTodo “action.”
Let’s create it by adding the following to the +page.server.js file we already have for Details (which currently has a load() function, to grab our to-do):
import { redirect } from "@sveltejs/kit"; // ... export const actions = { async editTodo({ request }) { const formData = await request.formData(); const id = formData.get("id"); const newTitle = formData.get("title"); await wait(250); updateTodo(id, newTitle); throw redirect(303, "/list"); }, };Form actions give us a request object, which provides access to our formData, which has a get method for our various form fields. We added that hidden input for the ID value so we could grab it here in order to look up the to-do item we’re editing. We simulate a delay, call a new updateTodo() method, then redirect the user back to the /list page. The updateTodo() method merely updates our static data; in real life you’d run some sort of update in whatever datastore you’re using.
export async function updateTodo(id, newTitle) { const todo = todos.find(t => t.id == id); Object.assign(todo, { title: newTitle }); }Let’s try it out. We’ll go to the List page first:
Now let’s click the Edit button for one of the to-do items to bring up the editing page in /details.
We’re going to add a new title:
Now, click Save. That should get us back to our /list page, with the new to-do title applied.
How did the new title show up like that? It was automatic. Once we redirected to the /list page, SvelteKit automatically re-ran all of our loaders just like it would have done regardless. This is the key advancement that next-gen application frameworks, like SvelteKit, Remix, and Next 13 provide. Rather than giving you a convenient way to render pages then wishing you the best of luck fetching whatever endpoints you might have to update data, they integrate data mutation alongside data loading, allowing the two to work in tandem.
A few things you might be wondering…
This mutation update doesn’t seem too impressive. The loaders will re-run whenever you navigate. What if we hadn’t added a redirect in our form action, but stayed on the current page? SvelteKit would perform the update in the form action, like before, but would still re-run all of the loaders for the current page, including the loaders in the page layout(s).
Can we have more targeted means of invalidating our data? For example, our tags were not edited, so in real life we wouldn’t want to re-query them. Yes, what I showed you is just the default forms behavior in SvelteKit. You can turn the default behavior off by providing a callback to use:enhance. Then SvelteKit provides manual invalidation functions.
Loading data on every navigation is potentially expensive, and unnecessary. Can I cache this data like I do with tools like react-query? Yes, just differently. SvelteKit lets you set (and then respect) the cache-control headers the web already provides. And I’ll be covering cache invalidation mechanisms in a follow-on post.
Everything we’ve done throughout this article uses static data and modifies values in memory. If you need to revert everything and start over, stop and restart the npm run dev Node process.
Wrapping upWe’ve barely scratched the surface of SvelteKit, but hopefully you’ve seen enough to get excited about it. I can’t remember the last time I’ve found web development this much fun. With things like bundling, routing, SSR, and deployment all handled out of the box, I get to spend more time coding than configuring.
Here are a few more resources you can use as next steps learning SvelteKit:
- Announcing SvelteKit 1.0 (Svelte Blog)
- Beginner SvelteKit Course (Vercel)
- SvelteKit Documentation
Getting Started With SvelteKit originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
How to Transition to Manifest V3 for Chrome Extensions
While I am not a regular Chrome extension programmer, I have certainly coded enough extensions and have a wide enough web development portfolio to know my way around the task. However, just recently, I had a client reject one of my extensions as I received feedback that my extension was “outdated”.
As I was scrambling to figure out what was wrong, I swept my embarrassment under the carpet and immediately began my deep dive back into the world of Chrome Extensions. Unfortunately, information on Manifest V3 was scarce and it was difficult for me to understand quickly what this transition was all about.
Needless to say, with a pending job, I had to painstakingly navigate my way around Google’s Chrome Developer Documentation and figure things out for myself. While I got the job done, I did not want my knowledge and research in this area to go to waste and decided to share what I wish I could have had easy access to in my learning journey.
Why the transition to Manifest 3 is importantManifest V3 is an API that Google will use in its Chrome browser. It is the successor to the current API, Manifest V2, and governs how Chrome extensions interact with the browser. Manifest V3 introduces significant changes to the rules for extensions, some of which will be the new mainstay from V2 we were used to.
The transition to Manifest V3 can be summarized as such:
- The transition has been ongoing since 2018.
- Manifest V3 will officially begin rolling out in January 2023.
- By June 2023, extensions that run Manifest V2 will no longer be available on the Chrome Web Store.
- Extensions that do not comply with the new rules introduced in Manifest V3 will eventually be removed from the Chrome Web Store.
One of the main goals of Manifest V3 is to make users safer and improve the overall browser experience. Previously, many browser extensions relied on code in the cloud, meaning it could be difficult to assess whether an extension was risky. Manifest V3 aims to address this by requiring extensions to contain all the code they will run, allowing Google to scan them and detect potential risks. It also forces extensions to request permission from Google for the changes they can implement on the browser.
Staying up-to-date with Google’s transition to Manifest V3 is important because it introduces new rules for extensions that aim to improve user safety and the overall browser experience, and extensions that do not comply with these rules will eventually be removed from the Chrome Web Store.
In short, all of your hard work in creating extensions that used Manifest V2 could be for naught if you do not make this transition in the coming months.
January 2023June 2023January 2024Support for Manifest V2 extensions will be turned off in Chrome’s Canary, Dev, and Beta channels.The Chrome Web Store will no longer allow Manifest V2 extensions to be published with visibility set to Public.The Chrome Web Store will remove all remaining Manifest V2 extensions.Manifest V3 will be required for the Featured badge in the Chrome Web Store.Existing Manifest V2 extensions that are published and publically visible will become unlisted.Support for Manifest 2 will end for all of Chrome’s channels, including the Stable channel, unless the Enterprise channel is extended. The key differences between Manifest V2 and V3There are many differences between the two, and while I highly recommend that you read up on Chrome’s “Migrating to Manifest V3” guide, here is a short and sweet summary of key points:
- Service workers replace background pages in Manifest V3.
- Network request modification is handled with the new declarativeNetRequest API in Manifest V3.
- In Manifest V3, extensions can only execute JavaScript that is included within their package and cannot use remotely-hosted code.
- Manifest V3 introduces promise support to many methods, though callbacks are still supported as an alternative.
- Host permissions in Manifest V3 are a separate element and must be specified in the "host_permissions" field.
- The content security policy in Manifest V3 is an object with members representing alternative content security policy (CSP) contexts, rather than a string as it was in Manifest V2.
In a simple Chrome Extension’s Manifest that alters a webpage’s background, that might look like this:
// Manifest V2 { "manifest_version": 2, "name": "Shane's Extension", "version": "1.0", "description": "A simple extension that changes the background of a webpage to Shane's face.", "background": { "scripts": ["background.js"], "persistent": true }, "browser_action": { "default_popup": "popup.html" }, "permissions": [ "activeTab", ], "optional_permissions": ["<all_urls>"] } // Manifest V3 { "manifest_version": 3, "name": "Shane's Extension", "version": "1.0", "description": "A simple extension that changes the background of a webpage to Shane's face.", "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html" }, "permissions": [ "activeTab", ], "host_permissions": [ "<all_urls>" ] }If you find some of the tags above seem foreign to you, keep reading to find out exactly what you need to know.
How to smoothly transition to Manifest V3I have summarized the transition to Manifest V3 in four key areas. Of course, while there are many bells and whistles in the new Manifest V3 that need to be implemented from the old Manifest V2, implementing changes in these four areas will get your Chrome Extension well on the right track for the eventual transition.
The four key areas are:
- Updating your Manifest’s basic structure.
- Modify your host permissions.
- Update the content security policy.
- Modify your network request handling.
With these four areas, your Manifest’s fundamentals will be ready for the transition to Manifest V3. Let’s look at each of these key aspects in detail and see how we can work towards future-proofing your Chrome Extension from this transition.
Updating your Manifest’s basic structureUpdating your manifest’s basic structure is the first step in transitioning to Manifest V3. The most important change you will need to make is changing the value of the "manifest_version" element to 3, which determines that you are using the Manifest V3 feature set.
One of the major differences between Manifest V2 and V3 is the replacement of background pages with a single extension service worker in Manifest V3. You will need to register the service worker under the "background" field, using the "service_worker" key and specify a single JavaScript file. Even though Manifest V3 does not support multiple background scripts, you can optionally declare the service worker as an ES Module by specifying "type": "module", which allows you to import further code.
In Manifest V3, the "browser_action" and "page_action" properties are unified into a single "action" property. You will need to replace these properties with "action" in your manifest. Similarly, the "chrome.browserAction" and "chrome.pageAction" APIs are unified into a single “Action” API in Manifest V3, and you will need to migrate to this API.
// Manifest V2 "background": { "scripts": ["background.js"], "persistent": false }, "browser_action": { "default_popup": "popup.html" }, // Manifest V3 "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html" }Overall, updating your manifest’s basic structure is a crucial step in the process of transitioning to Manifest V3, as it allows you to take advantage of the new features and changes introduced in this version of the API.
Modify your host permissionsThe second step in transitioning to Manifest V3 is modifying your host permissions. In Manifest V2, you specify host permissions in the "permissions" field in the manifest file. In Manifest V3, host permissions are a separate element, and you should specify them in the "host_permissions" field in the manifest file.
Here is an example of how to modify your host permissions:
// Manifest V2 "permissions": [ "activeTab", "storage", "http://www.css-tricks.com/", ":///*" ] // Manifest V3 "permissions": [ "activeTab", "scripting", "storage" ], "host_permissions": [ "http://www.css-tricks.com/" ], "optional_host_permissions": [ ":///*" ] Update the content security policyIn order to update the CSP of your Manifest V2 extension to be compliant with Manifest V3, you will need to make some changes to your manifest file. In Manifest V2, the CSP was specified as a string in the "content_security_policy" field of the manifest.
In Manifest V3, the CSP is now an object with different members representing alternative CSP contexts. Instead of a single "content_security_policy" field, you will now have to specify separate fields for "content_security_policy.extension_pages" and "content_security_policy.sandbox", depending on the type of extension pages you are using.
You should also remove any references to external domains in the "script-src", "worker-src", "object-src", and "style-src" directives if they are present. It is important to make these updates to your CSP in order to ensure the security and stability of your extension in Manifest V3.
// Manifest V2 "content_security_policy": "script-src 'self' https://css-tricks.com; object-src 'self'" // Manfiest V3 "content_security_policy.extension_pages": "script-src 'self' https://example.com; object-src 'self'", "content_security_policy.sandbox": "script-src 'self' https://css-tricks.com; object-src 'self'" Modify your network request handlingThe final step in transitioning to Manifest V3 is modifying your network request handling. In Manifest V2, you would have used the chrome.webRequest API to modify network requests. However, this API is replaced in Manifest V3 by the declarativeNetRequest API.
To use this new API, you will need to specify the declarativeNetRequest permission in your manifest and update your code to use the new API. One key difference between the two APIs is that the declarativeNetRequest API requires you to specify a list of predetermined addresses to block, rather than being able to block entire categories of HTTP requests as you could with the chrome.webRequest API.
It is important to make these changes in your code to ensure that your extension continues to function properly under Manifest V3. Here is an example of how you would modify your manifest to use the declarativeNetRequest API in Manifest V3:
// Manifest V2 "permissions": [ "webRequest", "webRequestBlocking" ] // Manifest V3 "permissions": [ "declarativeNetRequest" ]You will also need to update your extension code to use the declarativeNetRequest API instead of the chrome.webRequest API.
Other aspects you need to checkWhat I have covered is just the tip of the iceberg. Of course, if I wanted to cover everything, I could be here for days and there would be no point in having Google’s Chrome Developers guides. While what I covered will have you future-proofed enough to arm your Chrome extensions in this transition, here are some other things you might want to look at to ensure your extensions are functioning at the top of their game.
- Migrating background scripts to the service worker execution context: As mentioned earlier, Manifest V3 replaces background pages with a single extension service worker, so it may be necessary to update background scripts to adapt to the service worker execution context.
- Unifying the **chrome.browserAction** and **chrome.pageAction** APIs: These two equivalent APIs are unified into a single API in Manifest V3, so it may be necessary to migrate to the Action API.
- Migrating functions that expect a Manifest V2 background context: The adoption of service workers in Manifest V3 is not compatible with methods like chrome.runtime.getBackgroundPage(), chrome.extension.getBackgroundPage(), chrome.extension.getExtensionTabs(), and chrome.extension.getViews(). It may be necessary to migrate to a design that passes messages between other contexts and the background service worker.
- Moving CORS requests in content scripts to the background service worker: It may be necessary to move CORS requests in content scripts to the background service worker in order to comply with Manifest V3.
- Migrating away from executing external code or arbitrary strings: Manifest V3 no longer allows the execution of external logic using chrome.scripting.executeScript({code: '...'}), eval(), and new Function(). It may be necessary to move all external code (JavaScript, WebAssembly, CSS) into the extension bundle, update script and style references to load resources from the extension bundle, and use chrome.runtime.getURL() to build resource URLs at runtime.
- Updating certain scripting and CSS methods in the Tabs API: As mentioned earlier, several methods move from the Tabs API to the Scripting API in Manifest V3. It may be necessary to update any calls to these methods to use the correct Manifest V3 API.
And many more!
Feel free to take some time to get yourself up to date on all the changes. After all, this change is inevitable and if you do not want your Manifest V2 extensions to be lost due to avoiding this transition, then spend some time arming yourself with the necessary knowledge.
On the other hand, if you are new to programming Chrome extensions and looking to get started, a great way to go about it is to dive into the world of Chrome’s Web Developer tools. I did so through a course on Linkedin Learning, which got me up to speed pretty quickly. Once you have that base knowledge, come back to this article and translate what you know to Manifest V3!
So, how will I be using the features in the new Manifest V3 going forward?Well, to me, the transition to Manifest V3 and the removal of the chrome.webRequest API seems to be shifting extensions away from data-centric use cases (such as ad blockers) to more functional and application-based uses. I have been staying away from application development lately as it can get quite resource-intensive at times. However, this shift might be what brings me back!
The rise of AI tools in recent times, many with available-to-use APIs, has sparked tons of new and fresh SaaS applications. Personally, I think that it’s coming at a perfect time with the shift to more application-based Chrome extensions! While many of the older extensions may be wiped out from this transition, plenty of new ones built around novel SaaS ideas will come to take their place.
Hence, this is an exciting update to hop on and revamp old extensions or build new ones! Personally, I see many possibilities in using APIs that involve AI being used in extensions to enhance a user’s browsing experience. But that’s really just the tip of the iceberg. If you’re looking to really get into things with your own professional extensions or reaching out to companies to build/update extensions for them, I would recommend upgrading your Gmail account for the benefits it gives in collaborating, developing, and publishing extensions to the Chrome Web Store.
However, remember that every developer’s requirements are different, so learn what you need to keep your current extensions afloat, or your new ones going!
How to Transition to Manifest V3 for Chrome Extensions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Solved With :has(): Vertical Spacing in Long-Form Text
If you’ve ever worked on sites with lots of long-form text — especially CMS sites where people can enter screeds of text in a WYSIWYG editor — you’ve likely had to write CSS to manage the vertical spacing between different typographic elements, like headings, paragraphs, lists and so on.
It’s surprisingly tricky to get this right. And it’s one reason why things like the Tailwind Typography plugin and Stack Overflow’s Prose exist — although these handle much more than just vertical spacing.
Firefox supports :has() behind the layout.css.has-selector.enabled flag in about:config at the time of writing.
What makes typographic vertical spacing complicated?Surely it should just be as simple as saying that each element — p, h2, ul, etc. — has some amount of top and/or bottom margin… right? Sadly, this isn’t the case. Consider this desired behavior:
- The first and last elements in a block of long-form text shouldn’t have any extra space above or below (respectively). This is so that other, non-typographic elements are still placed predictably around the long-form content.
- Sections within the long-form content should have a nice big space between them. A “section” being a heading and all the following content that belongs to that heading. In practice, this means having a nice big space before a heading… but not if that heading is immediately preceded by another heading!
You need to look no further than right here at CSS-Tricks to see where this could come in handy. Here are a couple of screenshots of spacing I pulled from another article.
The vertical spacing between Heading 2 and Heading 3 The vertical space between Heading 3 and a paragraph The traditional solutionThe typical solution I’ve seen involves putting any long-form content in a wrapping div (or a semantic tag, if appropriate). My go-to class name has been .rich-text, which I think I use as a hangover from older versions of the Wagtail CMS, which would add this class automatically when rendering WYSIWYG content. Tailwind Typography uses a .prose class (plus some modifier classes).
Then we add CSS to select all typographic elements in that wrapper and add vertical margins. Noting, of course, the special behavior mentioned above to do with stacked headings and the first/last element.
CodePen Embed FallbackThe traditional solution sounds reasonable… what’s the problem? I think there are a few…
Rigid structureHaving to add a wrapper class like .rich-text in all the right places means baking in a specific structure to your HTML code. That’s sometimes necessary, but it feels like it shouldn’t have to be in this particular case. It can also be easy to forget to do this everywhere you need to, especially if you need to use it for a mix of CMS and hard-coded content.
The HTML structure gets even more rigid when you want to be able to trim the top and bottom margin off the first and last elements, respectively, because they need to be immediate children of the wrapper element, e.g., .rich-text > *:first-child. That > is important — after all, we don’t want to accidentally select the first list item in each ul or ol with this selector.
Mixing margin propertiesIn the pre-:has() world, we haven’t had a way to select an element based on what follows it. Therefore, the traditional approach to spacing typographic elements involves using a mix of both margin-top and margin-bottom:
- We start by setting our default spacing to elements with margin-bottom.
- Next, we space out our “sections” using margin-top — i.e. very big space above each heading
- Then we override those big margin-tops when a heading is followed immediately by another heading using the adjacent sibling selector (e.g. h2 + h3).
Now, I don’t know about you, but I’ve always felt it’s better to use a single margin direction when spacing things out, generally favoring margin-bottom (that’s assuming the CSS gap property isn’t feasible, which it is not in this case). Whether this is a big deal, or even true, I’ll let you decide. But personally, I’d rather be setting margin-bottom for spacing long-form content.
Collapsing marginsBecause of collapsing margins, this mix of top and bottom margins isn’t a big problem per se. Only the larger of two stacked margins will take effect, not the sum of both margins. But… well… I don’t really like collapsing margins.
Collapsing margins are yet one more thing to be aware of. It might be confusing for junior devs who aren’t up to speed with that CSS quirk. The spacing will totally change (i.e. stop collapsing) if you were to change the wrapper to a flex layout with flex-direction: column for instance, which is something that wouldn’t happen if you set your vertical margins in a single direction.
I more-or-less know how collapsing margins work, and I know that they’re there by design. I also know they’ve made my life easier on occasion. But they’ve also made it harder other times. I just think they’re kinda weird, and I’d generally rather avoid relying on them.
The :has() solutionAnd here is my attempt at solving these issues with :has().
CodePen Embed FallbackTo recap the improvements this aims to make:
- No wrapper class is required.
- We’re working with a consistent margin direction.
- Collapsing margins are avoided (which may or may not be an improvement, depending on your stance).
- There’s no setting styles and then immediately overriding them.
- Always check browser support. At time of writing, Firefox only supports :has() behind an experimental flag.
- My solution doesn’t include all possible typographic elements. For instance, there’s no <blockquote> in my demo. The selector list is easy enough to extend though.
- My solution also doesn’t handle non-typographic elements that may be present in your particular long-form text blocks, e.g. <img>. That’s because for the sites I work on, we tend to lock down the WYSIWYG as much as possible to core text nodes, like headings, paragraphs, and lists. Anything else — e.g. quotes, images, tables, etc. — is a separate CMS component block, and those blocks themselves are spaced apart from each other when rendered on a page. But again, the selector list can be extended.
- I’ve only included h1 for the sake of completeness. I usually wouldn’t allow a CMS user to add an h1 via WYSIWYG, as the page title would be baked into the page template somewhere rather than entered in the CMS page editor.
- I’m not catering for a heading followed immediately by the same level heading (h2 + h2). This would mean that the first heading wouldn’t “own” any content, which seems like a misuse of headings (and, correct me if I’m wrong, but it might violate WCAG 1.3.1 Info and Relationships). I’m also not catering for skipped heading levels, which are invalid.
- I am in no way knocking the existing approaches I mentioned. If and when I build another Tailwind site I’ll use the excellent Typography plugin, no question!
- I’m not a designer. I came up with these spacing values by eyeballing it. You probably could (and should) use better values.
I was going to write a whole big thing here about how the traditional method and the new :has() way of doing it might fit into the ITCSS methodology… But now that we have :where() (the zero-specificity selector) you can pretty much choose your preferred level of specificity for any selector now.
That said, the fact that we’re no longer dealing with a wrapper — .prose, .rich-text, etc. — to me makes it feel like this should live in the “elements” layer, i.e. before you start dealing with class-level specificity. I’ve used :where() in my examples to keep specificity consistent. All the selectors in both of my examples have a specificity score of 0,0,1 (except for the bare-bones reset).
Wrapping upSo there you have it, a bleeding-edge solution to a very boring problem! This newer approach is still not what I’d call “simple” CSS — as I said at the beginning, it’s a more complex topic than it might seem at first. But aside from having a few slightly complex selectors, I think the new approach makes more sense overall, and the less rigid HTML structure seems very appealing.
If you end up using this, or something like it, I’d love to know how it works out for you. And if you can think of ways to improve it, I’d love to hear those too!
Solved With :has(): Vertical Spacing in Long-Form Text originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
6 Common SVG Fails (and How to Fix Them)
Someone recently asked me how I approach debugging inline SVGs. Because it is part of the DOM, we can inspect any inline SVG in any browser DevTools. And because of that, we have the ability to scope things out and uncover any potential issues or opportunities to optimize the SVG.
But sometimes, we can’t even see our SVGs at all. In those cases, there are six specific things that I look for when I’m debugging.
1. The viewBox valuesThe viewBox is a common point of confusion when working with SVG. It’s technically fine to use inline SVG without it, but we would lose one of its most significant benefits: scaling with the container. At the same time, it can work against us when improperly configured, resulting in unwanted clipping.
The elements are there when they’re clipped — they’re just in a part of the coordinate system that we don’t see. If we were to open the file in some graphics editing program, it might look like this:
Screenshot of SVG opened in Illustrator.The easiest way to fix this? Add overflow="visible" to the SVG, whether it’s in our stylesheet, inline on the style attribute or directly as an SVG presentation attribute. But if we also apply a background-color to the SVG or if we have other elements around it, things might look a little bit off. In this case, the best option will be to edit the viewBox to show that part of the coordinate system that was hidden:
Demo applying overflow="hidden" and editing the viewBox.There are a few additional things about the viewBox that are worth covering while we’re on the topic:
How does the viewBox work?SVG is an infinite canvas, but we can control what we see and how we see it through the viewport and the viewBox.
The viewport is a window frame on the infinite canvas. Its dimensions are defined by width and height attributes, or in CSS with the corresponding width and height properties. We can specify any length unit we want, but if we provide unitless numbers, they default to pixels.
The viewBox is defined by four values. The first two are the starting point at the upper-left corner (x and y values, negative numbers allowed). I’m editing these to reframe the image. The last two are the width and height of the coordinate system inside the viewport — this is where we can edit the scale of the grid (which we’ll get into in the section on Zooming).
Here’s simplified markup showing the SVG viewBox and the width and height attributes both set on the <svg>:
<svg viewBox="0 0 700 700" width="700" height="700"> <!-- etc. --> </svg> ReframingSo, this:
<svg viewBox="0 0 700 700">…maps to this:
<svg viewBox="start-x-axis start-y-axis width height">The viewport we see starts where 0 on the x-axis and 0 on the y-axis meet.
By changing this:
<svg viewBox="0 0 700 700">…to this:
<svg viewBox="300 200 700 700">…the width and height remain the same (700 units each), but the start of the coordinate system is now at the 300 point on the x-axis and 200 on the y-axis.
In the following video I’m adding a red <circle> to the SVG with its center at the 300 point on the x-axis and 200 on the y-axis. Notice how changing the viewBox coordinates to the same values also changes the circle’s placement to the upper-left corner of the frame while the rendered size of the SVG remains the same (700×700). All I did was “reframe” things with the viewBox.
ZoomingWe can change the last two values inside the viewBox to zoom in or out of the image. The larger the values, the more SVG units are added to fit in the viewport, resulting in a smaller image. If we want to keep a 1:1 ratio, our viewBox width and height must match our viewport width and height values.
Let’s see what happens in Illustrator when we change these parameters. The artboard is the viewport which is represented by a white 700px square. Everything else outside that area is our infinite SVG canvas and gets clipped by default.
Figure 1 below shows a blue dot at 900 along the x-axis and 900 along the y-axis. If I change the last two viewBox values from 700 to 900 like this:
<svg viewBox="300 200 900 900" width="700" height="700">…then the blue dot is almost fully back in view, as seen in Figure 2 below. Our image is scaled down because we increased the viewBox values, but the SVG’s actual width and height dimensions remained the same, and the blue dot made its way back closer to the unclipped area.
Figure 1 Figure 2There is a pink square as evidence of how the grid scales to fit the viewport: the unit gets smaller, and more grid lines fit into the same viewport area. You can play with the same values in the following Pen to see that work in action:
CodePen Embed Fallback 2. Missing width and heightAnother common thing I look at when debugging inline SVG is whether the markup contains the width or height attributes. This is no big deal in many cases unless the SVG is inside a container with absolute positioning or a flexible container (as Safari computes the SVG width value with 0px instead of auto). Excluding width or height in these cases prevents us from seeing the full image, as we can see by opening this CodePen demo and comparing it in Chrome, Safari, and Firefox (tap images for larger view).
Chrome Safari FirefoxThe solution? Add a width or height, whether as a presentation attribute, inline in the style attribute, or in CSS. Avoid using height by itself, particularly when it is set to 100% or auto. Another workaround is to set the right and left values.
You can play around with the following Pen and combine the different options.
CodePen Embed Fallback 3. Inadvertent fill and stroke colorsIt may also be that we are applying color to the <svg> tag, whether it’s an inline style or coming from CSS. That’s fine, but there could be other color values throughout the markup or styles that conflict with the color set on the <svg>, causing parts to be invisible.
That’s why I tend to look for the fill and stroke attributes in the SVG’s markup and wipe them out. The following video shows an SVG I styled in CSS with a red fill. There are a couple of instances where parts of the SVG are filled in white directly in the markup that I removed to reveal the missing pieces.
4. Missing IDsThis one might seem super obvious, but you’d be surprised how often I see it come up. Let’s say we made an SVG file in Illustrator and were very diligent about naming our layers so that you get nice matching IDs in the markup when exporting the file. And let’s say we plan to style that SVG in CSS by hooking into those IDs.
That’s a nice way to do things. But there are plenty of times where I’ve seen the same SVG file exported a second time to the same location and the IDs are different, usually when copy/pasting the vectors directly. Maybe a new layer was added, or one of the existing ones was renamed or something. Whatever the case, the CSS rules no longer match the IDs in the SVG markup, causing the SVG to render differently than you’d expect.
Pasting Illustrator’s exported SVG file into SVGOMG.In large SVG files we might find it difficult to find those IDs. This is a good time to open the DevTools, inspect that part of the graphic that’s not working, and see if those IDs are still matching.
So, I’d say it’s worth opening an exported SVG file in a code editor and comparing it to the original before swapping things out. Apps like Illustrator, Figma, and Sketch are smart, but that doesn’t mean we aren’t responsible for vetting them.
5. Checklist for clipping and maskingIf an SVG is unexpectedly clipped and the viewBox checks out alright, I usually look at the CSS for clip-path or mask properties that might interfere with the image. It’s tempting to keep looking at the inline markup, but it’s good to remember that an SVG’s styling might be happening elsewhere.
CSS clipping and masking allow us to “hide” parts of an image or element. In SVG, <clipPath> is a vector operation that cuts parts of an image with no halfway results. The <mask> tag is a pixel operation that allows transparency, semi-transparency effects, and blurred edges.
This is a small checklist for debugging cases where clipping and masking are involved:
- Make sure the clipping path (or mask) and the graphic overlap one another. The overlapping parts are what gets displayed.
- If you have a complex path that is not intersecting your graphic, try applying transforms until they match.
- You can still inspect the inner code with the DevTools even though the <clipPath> or <mask> are not rendered, so use it!
- Copy the markup inside <clipPath> and <mask> and paste it before closing the </svg> tag. Then add a fill to those shapes and check the SVG’s coordinates and dimensions. If you still do not see the image, try adding overflow="hidden" to the <svg> tag.
- Check that a unique ID is used for the <clipPath> or <mask>, and that the same ID is applied to the shapes or group of shapes that are clipped or masked. A mismatched ID will break the appearance.
- Check for typos in the markup between the <clipPath> or <mask> tags.
- fill, stroke, opacity, or some other styles applied to the elements inside <clipPath> are useless — the only useful part is the fill-region geometry of those elements. That’s why if you use a <polyline> it will behave as a <polygon> and if you use a <line> you won’t see any clipping effect.
- If you don’t see your image after applying a <mask>, make sure that the fill of the masking content is not entirely black. The luminance of the masking element determines the opacity of the final graphic. You’ll be able to see through the brighter parts, and the darker parts will hide your image’s content.
You can play with masked and clipped elements in this Pen.
6. NamespacesDid you know that SVG is an XML-based markup language? Well, it is! The namespace for SVG is set on the xmlns attribute:
<svg xmlns="http://www.w3.org/2000/svg"> <!-- etc. --> </svg>There’s a lot to know about namespacing in XML and MDN has a great primer on it. Suffice to say, the namespace provides context to the browser, informing it that the markup is specific to SVG. The idea is that namespaces help prevent conflicts when more than one type of XML is in the same file, like SVG and XHTML. This is a much less common issue in modern browsers but could help explain SVG rendering issues in older browsers or browsers like Gecko that are strict when defining doctypes and namespaces.
The SVG 2 specification does not require namespacing when using HTML syntax. But it’s crucial if support for legacy browsers is a priority — plus, it doesn’t hurt anything to add it. That way, when the <html> element’s xmlns attribute is defined, it will not conflict in those rare cases.
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <body> <svg xmlns="http://www.w3.org/2000/svg" width="700px" height="700px"> <!-- etc. --> </svg> </body> </html>This is also true when using inline SVG in CSS, like setting it as a background image. In the following example, a checkmark icon appears on the input after successful validation. This is what the CSS looks like:
textarea:valid { background: white url('data:image/svg+xml,\ <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26">\ <circle cx="13" cy="13" r="13" fill="%23abedd8"/>\ <path fill="none" stroke="white" stroke-width="2" d="M5 15.2l5 5 10-12"/>\ </svg>') no-repeat 98% 5px; }When we remove the namespace inside the SVG in the background property, the image disappears:
Another common namespace prefix is xlink:href. We use it a lot when referencing other parts of the SVG like: patterns, filters, animations or gradients. The recommendation is to start replacing it with href as the other one is being deprecated since SVG 2, but there might be compatibility issues with older browsers. In that case, we can use both. Just remember to include the namespace xmlns:xlink="http://www.w3.org/1999/xlink" if you are still using xlink:href.
Level up your SVG skills!I hope these tips help save you a ton of time if you find yourself troubleshooting improperly rendered inline SVGs. These are just the things I look for. Maybe you have different red flags you watch for — if so, tell me in the comments!
The bottom line is that it pays to have at least a basic understanding of the various ways SVG can be used. CodePen Challenges often incorporate SVG and offer good practice. Here are a few more resources to level up:
- Using SVG with CSS3 and HTML5 (Amelia Bellamy-Royds, Kurt Cagle, Dudley Storey) — I consider it the SVG Bible.
- Lea Verou has a wealth of SVG knowledge and has spoken on the topic quite a bit (like this video from Frontend United 2019).
- SVG Animations (Sarah Drasner)
- SVG Essentials (Amelia Bellamy-Royds, J. David Eisenberg)
- Practical SVG (Chris Coyier)
There are a few people I suggest following for SVG-related goodness:
6 Common SVG Fails (and How to Fix Them) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
:has is an unforgiving selector
A little thing happened on the way to publishing the CSS :has() selector to the ol’ Almanac. I had originally described :has() as a “forgiving” selector, the idea being that anything in its argument is evaluated, even if one or more of the items is invalid.
/* Example: Do not use! */ article:has(h2, ul, ::-scoobydoo) { }See ::scoobydoo in there? That’s totally invalid. A forgiving selector list ignores that bogus selector and proceeds to evaluate the rest of the items as if it were written like this:
article:has(h2, ul) { }:has() was indeed a forgiving selector in a previous draft dated May 7, 2022. But that changed after an issue was reported that the forgiving nature conflicts with jQuery when :has() contains a complex selector (e.g. header h2 + p). The W3C landed on a resolution to make :has() an “unforgiving” selector just a few weeks ago.
So, our previous example? The entire selector list is invalid because the bogus selector is invalid. But the other two forgiving selectors, :is() and :where(), are left unchanged.
There’s a bit of a workaround for this. Remember, :is() and :where()are forgiving, even if :has() is not. That means we can nest either of the those selectors in :has() to get more forgiving behavior:
article:has(:where(h2, ul, ::-scoobydoo)) { }Which one you use might matter because the specificity of :is() is determined by the most specific item in its list. So, if you need to something less specific you’d do better reaching for :where() since it does not add to the specificity score.
/* Specificity: (0,0,1) */ article:has(:where(h2, ul, ::-scoobydoo)) { } /* Specificity: (0,0,2) */ article:has(:is(h2, ul, ::-scoobydoo)) { } CodePen Embed FallbackWe updated a few of our posts to reflect the latest info. I’m seeing plenty of others in the wild that need to be updated, so just a little PSA for anyone who needs to do the same.
:has is an unforgiving selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Faking Min Width on a Table Column
The good ol’ <table> tag is the most semantic HTML for showing tabular data. But I find it very hard to control how the table is presented, particularly column widths in a dynamic environment where you might not know how much content is going into each table cell. In some cases, one column is super wide while others are scrunched up. Other times, we get equal widths, but at the expense of a column that contains more content and needs more space.
But I found a CSS tricks-y workaround that helps make things a little easier. That’s what I want to show you in this post.
The problemFirst we need to understand how layout is handled by the browser. We have the table-layout property in CSS to define how a table should distribute the width for each table column. It takes one of two values:
- auto (default)
- fixed
Let us start with a table without defining any widths on its columns. In other words, we will let the browser decide how much width to give each column by applying table-layout: auto on it in CSS. As you will notice, the browser does its best with the algorithm it has to divide the full available width between each column.
CodePen Embed FallbackIf we swap out an auto table layout with table-layout: fixed, then the browser will merely divide the full available space by the total number of columns, then apply that value as the width for each column:
CodePen Embed FallbackBut what if we want to control the widths of our columns? We have the <colgroup> element to help! It consists of individual <col> elements we can use to specify the exact width we need for each column. Let’s see how that works in with table-layout: auto:
CodePen Embed FallbackI have inlined the styles for the sake of illustration.
The browser is not respecting the inline widths since they exceed the amount of available table space when added up. As a result, the table steals space from the columns so that all of the columns are visible. This is perfectly fine default behavior.
How does <colgroup> work with table-layout: fixed. Let’s find out:
CodePen Embed FallbackThis doesn’t look good at all. We need the column with a bunch of content in it to flex a little while maintaining a fixed width for the rest of the columns. A fixed table-layout value respects the width — but so much so that it eats up the space of the column that needs the most space… which is a no-go for us.
This could easily be solved if only we could set a min-width on the column instead of a width. That way, the column would say, “I can give all of you some of my width until we reach this minimum value.“ Then the table would simply overflow its container and give the user a horizontal scroll to display the rest of the table. But unfortunately, min-width on table columns are not respected by the <col> element.
The solutionThe solution is to fake a min-width and we need to be a bit creative to do it.
We can add an empty <col> as the second column for our <colgroup> in the HTML and apply a colspan attribute on the first column so that the first column takes up the space for both columns:
<table> <colgroup> <col class="col-200" /> <col /> <col class="col-input" /> <col class="col-date" /> <col class="col-edit" /> </colgroup> <thead> <tr> <th colspan="2">Project name</th> <th>Amount</th> <th>Date</th> <th>Edit</th> </tr> </thead> <!-- etc. --> </table>Note that I have added classes in place of the inline styles from the previous example. The same idea still applies: we’re applying widths to each column.
The trick is that relationship between the first <col> and the empty second <col>. If we apply a width to the first <col> (it’s 200px in the snippet above), then the second column will be eaten up when the fixed table layout divides up the available space to distribute to the columns. But the width of the first column (200px) is respected and remains in place.
Voilà! We have a faux min-width set on a table cell. The first cell flexes as the available space changes and the table overflows for horizontal scrolling just as we hoped it would.
CodePen Embed Fallback(I added a little sticky positioning to the first column there.)
AccessibilityLet’s not totally forget about accessibility here. I ran the table through NVDA on Windows and VoiceOver on macOS and found that all five columns are announced, even if we’re only using four of them. And when the first column is in focus, it announces, “Column one through two”. Not perfectly elegant but also not going to cause someone to get lost. I imagine we could throw an aria-hidden attribute on the unused column, but also know ARIA isn’t a substitute for poor HTML.
I’ll admit, this feels a little, um, hacky. But it does work! Let me know if you have a different approach in the comments… or know of any confusions this “hack” might bring to our users.
Faking Min Width on a Table Column originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Styling Buttons in WordPress Block Themes
A little while back, Ganesh Dahal penned a post here on CSS-Tricks responding to a tweet that asked about adding CSS box shadows on WordPress blocks and elements. There’s a lot of great stuff in there that leverages new features that shipped in WordPress 6.1 that provide controls for applying shadows to things directly in the Block Editor and Site Editor UI.
Ganesh touched briefly on button elements in that post. I want to pick that up and go deeper into approaches for styling buttons in WordPress block themes. Specifically, we’re going to crack open a fresh theme.json file and break down various approaches to styling buttons in the schema.
Why buttons, you ask? That’s a good question, so let’s start with that.
The different types of buttonsWhen we’re talking about buttons in the context of the WordPress Block Editor, we have to distinguish between two different types:
- Child blocks inside of the Buttons block
- Buttons that are nested inside other block (e.g. the Post Comments Form block)
If we add both of these blocks to a template, they have the same look by default.
But the markup is very different:
<div class="wp-block-button"> <a class="wp-block-button__link wp-element-button">Button 1</a> </div> <p class="form-submit wp-block-button"> <input name="submit" type="submit" id="submit" class="wp-block-button__link wp-element-button" value="Post Comment"> </p>As we can see, the HTML tag names are different. It’s the common classes — .wp-block-button and .wp-element-button — that ensure consistent styling between the two buttons.
If we were writing CSS, we would target these two classes. But as we know, WordPress block themes have a different way of managing styles, and that’s through the theme.json file. Ganesh also covered this in great detail, and you’d do well giving his article a read.
So, how do we define button styles in theme.json without writing actual CSS? Let’s do it together.
Creating the base stylestheme.json is a structured set of schema written in property:value pairs. The top level properties are called “sections”, and we’re going to work with the styles section. This is where all the styling instructions go.
We’ll focus specifically on the elements in the styles. This selector targets HTML elements that are shared between blocks. This is the basic shell we’re working with:
// theme.json { "version": 2, "styles": { "elements": { // etc. } } }So what we need to do is define a button element.
={ "version": 2, "styles": { "elements": { "button": { // etc. } } } }That button corresponds to HTML elements that are used to mark up button elements on the front end. These buttons contain HTML tags that could be either of our two button types: a standalone component (i.e. the Button block) or a component nested within another block (e.g. the Post Comment block).
Rather than having to style each individual block, we create shared styles. Let’s go ahead and change the default background and text color for both types of buttons in our theme. There’s a color object in there that, in turn, supports background and text properties where we set the values we want:
{ "version": 2, "styles": { "elements": { "button": { "color": { "background": "#17a2b8", "text": "#ffffff" } } } } }This changes the color of both button types:
If crack open DevTools and have a look at the CSS that WordPress generates for the buttons, we see that the .wp-element-button class adds the styles we defined in theme.json:
.wp-element-button { background-color: #17a2b8; color: #ffffff; }Those are our default colors! Next, we want to give users visual feedback when they interact with the button.
Implementing interactive button stylesSince this is a site all about CSS, I’d bet many of you are already familiar with the interactive states of links and buttons. We can :hover the mouse cursor over them, tab them into :focus, click on them to make them :active. Heck, there’s even a :visited state to give users a visual indication that they’ve clicked this before.
Those are CSS pseudo-classes and we use them to target a link’s or button’s interactions.
In CSS, we might style a :hover state like this:
a:hover { /* Styles */ }In theme.json, we’re going to extend our existing button declaration with these pseudo-classes.
{ "version": 2, "styles": { "elements": { "button": { "color": { "background": "#17a2b8", "text": "#ffffff" }, ":hover": { "color": { "background": "#138496" } }, ":focus": { "color": { "background": "#138496" } }, ":active": { "color": { "background": "#138496" } } } } } }Notice the “structured” nature of this. We’re basically following an outline:
- Elements
- Element
- Object
- Property
- Value
- Property
- Object
- Element
We now have a complete definition of our button’s default and interactive styles. But what if we want to style certain buttons that are nested in other blocks?
Styling buttons nested in individual blocksLet’s imagine that we want all buttons to have our base styles, with one exception. We want the submit button of the Post Comment Form block to be blue. How would we achieve that?
This block is more complex than the Button block because it has more moving parts: the form, inputs, instructive text, and the button. In order to target the button in this block, we have to follow the same sort of JSON structure we did for the button element, but applied to the Post Comment Form block, which is mapped to the core/post-comments-form element:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { // etc. } } } }Notice that we’re no longer working in elements anymore. Instead, we’re working inside blocks which is reserved for configuring actual blocks. Buttons, by contrast, are considered a global element since they can be nested in blocks, even though they are available as a standalone block too.
The JSON structure supports elements within elements. So, if there’s a button element in the Post Comment Form block, we can target it in the core/post-comments-form block:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { "elements": { "button": { "color": { "background": "#007bff" } } } } } } }This selector means that not only are we targeting a specific block — we’re targeting a specific element that is contained in that block. Now we have a default set of button styles that are applied to all buttons in the theme, and a set of styles that apply to specific buttons that are contained in the Post Comment Form block.
The CSS generated by WordPress has a more precise selector as a result:
.wp-block-post-comments-form .wp-element-button, .wp-block-post-comments-form .wp-block-button__link { background-color: #007bff; }And what if we want to define different interactive styles for the Post Comment Form button? It’s the same deal as the way we did it for the default styles, only those are defined inside the core/post-comments-form block:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { "elements": { "button": { "color": { "background": "#007bff" } ":hover": { "color": { "background": "#138496" } }, // etc. } } } } } } What about buttons that are not in blocks?WordPress automagically generates and applies the right classes to output these button styles. But what if you use a “hybrid” WordPress theme that supports blocks and full-site editing, but also contains “classic” PHP templates? Or what if you made a custom block, or even have a legacy shortcode, that contains buttons? None of these are handled by the WordPress Style Engine!
No worries. In all of those cases, you would add the .wp-element-button class in the template, block, or shortcode markup. The styles generated by WordPress will then be applied in those instances.
And there may be some situations where you have no control over the markup. For example, some block plugin might be a little too opinionated and liberally apply its own styling. That’s where you can typically go to the “Advanced” option in the block’s settings panel and apply the class there:
Wrapping upWhile writing “CSS” in theme.json might feel awkward at first, I’ve found that it becomes second nature. Like CSS, there are a limited number of properties that you can apply either broadly or very narrowly using the right selectors.
And let’s not forget the three main advantages of using theme.json:
- The styles are applied to buttons in both the front-end view and the block editor.
- Your CSS will be compatible with future WordPress updates.
- The generated styles work with block themes and classic themes alike — there’s no need to duplicate anything in a separate stylesheet.
If you have used theme.json styles in your projects, please share your experiences and thoughts. I look forward to reading any comments and feedback!
Styling Buttons in WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Steven Heller’s Font of the Month: Edie & Eddy Slab
Read the book, Typographic Firsts
Steven Heller takes a closer look at Lisa Fischbach’s wonderful new slab serif font family, Edie and Eddy Slab.
The post Steven Heller’s Font of the Month: Edie & Eddy Slab appeared first on I Love Typography.
Thank You (2022 Edition)
You know, this is the time of year where Chris normally publishes a big ol’ reflection of the past year. The first one was published in 2007, the same year CSS-Tricks began, and it continued all the way through 2021 without missing a beat. Having been a CSS-Tricks reader myself all those years, I’d hate to see that change.
So, here we are! 2022 was sure a heckuva year as far as transition goes. At this time last year, we were looking ahead at some goals for the upcoming year (which we’ll get to) but wound up joining DigitalOcean. That was a massive sea change (pun intended) and yet you stuck with us the whole way. It really means a lot to me that so many of you come here to read the things that I and a slew of other guest writers publish here. It just ain’t a community (or any fun) without y’all.
Thank you!
This is the last post we’re publishing this year as we hang up our Gone Fishin’ sign for the holidays. We’ll be back January 9 with a spate of content that’s fresh from the oven.
OK, this is where we start digging into the site’s analytics. That’s something we’ve always been transparent about and will continue to do. It’s not like our numbers are in some off-limits black box, and the hope is that sharing them somehow helps you and your business.
Overall trafficOverall, Google Analytics is showing a total of 64m pageviews for the entire year. That’s wayyyyy down from last year’s 88m, which is alarming at first glance. I mean, who wants to see a 27% drop in year-over-year traffic?
But there’s good reason for that because we published wayyyy less content this year. We all know Chris was a prolific writer (and still is, of course), often spitting out multiple posts a day. It’s sorta like we lost our most productive contributor for the bulk of the year. Let’s compare the publishing activity for the last few years:
- 2020: 1,183 articles
- 2021: 890 articles
- 2022: 390 articles
A 27% drop in pageviews is a lot less concerning considering we published 43% fewer articles than last year, and a whopping 67% fewer than 2020’s overall total.
Hmm, I don’t feel like I’m working 67% less…
And all of this comes with the caveat that this is just what we get from Google Analytics. In past years, Chris has compared those numbers with stats from Cloudflare (the CDN layer that sits on top of the site) and Jetpack (the plugin that connects our self-hosted WordPress site to WordPress.com’s SaaS-y features). The results are always consistently inconsistent to the extent that I’m not even bothering to look this time around. (Alright, alright maybe just Jetpack… which shows 59.9m pageviews — oddly more than 2021’s 55m total.)
Articles, by the numbersThis is what I always look forward to each year! Here are the top ten articles in 2022 that were published in 2022:
- 6 Creative Ideas for CSS Link Hover Effects — Harshil Patel
- Explain the First 10 Lines of Twitter’s Source Code to Me — Anand Chowdhary
- What Were the Hottest Front-End Tools in 2021? — Louis Lazaris
- Replace JavaScript Dialogs With the New HTML Dialog Element — Mads Stoumann
- Say Hello to selectmenu, a Fully Style-able select Element — Patrick Brosset
- Reliably Send an HTTP Request as a User Leaves a Page — Alex MacArthur
- grid-template-columns — Mojtaba Seyedi
- A Complete Guide to CSS Cascade Layers — Miriam Suzanne
- CSS Database Queries? Sure We Can! — Chris Coyier
- CSS-Tricks is joining DigitalOcean! — Chris Coyier
I’m actually surprised that last one wasn’t higher on the list. And I’m really stoked to see one from the Almanac in there, especially because Mojtaba chipped away at all of the CSS Grid properties over the past year and half and he knocked it way out of the ballpark. I thought I had a good handle on grid until I started reading all of the gold nuggets he packed into each property. There’s so much to learn in there and Mojtaba has a knack for clearly explaining complicated things. I’m hoping to update the CSS Grid guide with all that fresh information (but more on that in a bit).
I love seeing the CSS Cascade Layers guide in there, too! I had so much fun working with Miriam on it. If you didn’t know it, she’s an editor for the spec. It’s a treat (and honor, really) to host her work here and make it available for us all to bookmark and reference.
Here’s 11-20 for kicks:
- Animation With Basic JavaScript —Md Shuvo
- Flutter For Front-End Web Developers —Obumuneme Nwabude
- CSS Grid and Custom Shapes, Part 1 — Temani Afif
- Write HTML, the HTML Way (Not the XHTML Way) — Jens Oliver Meiert
- A Whistle-Stop Tour of 4 New CSS Color Features — Chris Coyier
- Cool Hover Effects That Use Background Properties — Temani Afif
- Let’s Create a Tiny Programming Language — Md Shuvo
- Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D — Temani Afif
- A Perfect Table of Contents With HTML + CSS — Nicholas C. Zakas
- CSS-Based Fingerprinting — Chris Coyier
All posts that were published in 2022 make up 4.8m pageviews, or about 7.8% of all pageviews. Our most viewed article is always the ol’ Flexbox guide which garnered 5.8m views this year. I’d love to see our new content outpace that one item, and I believe that would’ve easily happened if we’d kept up the pace of publishing. Back of the napkin math here, but we may have been around 67m pageviews if we had published 540 more articles to match last year’s number of published articles.
If we take a few steps back, then we can see the most-viewed articles from the past year, regardless of when they were published:
- A Complete Guide to Flexbox
- A Complete Guide to Grid
- Perfect Full Page Background Image
- The Shapes of CSS
- Media Queries for Standard Devices
- Using SVG
- How to Scale SVG
- CSS Triangle
- Gradient Borders in CSS
- Truncate String with Ellipsis
- How to use @font-face in CSS
Yep, nearly identical to last year. And the year before. And the year before. And… well, almost. “Gradient Borders in CSS” is new, bumping the box-shadow property off the list. Everything else from the four spot on merely swapped places.
Speaking of the properties in the Almanac, I wanna see what y’all referenced most this past year:
- ::after / ::before
- transition
- box-shadow
- scrollbar
- justify-content
- flex-wrap
- gap
- overflow-wrap
- animation
- white-space
One pseudo at the top and nothing but properties after that. Interesting, given that relational pseudo selector functions like :has(), :is(), and :where() are new kids on the block.
NewsletterThe numbers here are way too messy to draw any insightful conclusions. After moving to DigitalOcean, we had to scrub our list of 91K+ subscribers for compliance purposes and the number plummeted as a result. If you were dropped from the list, you can re-subscribe here.
The good news? We’re still doing the newsletter! We actually fired it back up in August after a five-month hiatus. We were on a weekly cadence, but are at once a month now while yours truly is authoring it. I sure hope to bump it back to a weekly publication. (I miss you, Robin!)
Site updatesIt’s been mostly about keeping the ship afloat, if I’m being honest. Other than some minor tweaks and maintenance, the site is pretty much where it was at this time last year.
That will change big time in 2023. If you’ve been keeping up with our monthly Behind the CSScenes updates, then you know that we’re planning to migrate CSS-Tricks from WordPress to the same homespun CMS that DigitalOcean uses for all of its (stellar) community content.
That work kicked off a couple months ago and should be done within the first half of the year. You can bet that we’ll keep you updated along the way. Besides a fresh design and a new back-end, it should be business as usual. If you have any questions about that work and what it means for your favorite front-end publication, please do hit me up in the comments or shoot me an email.
If you haven’t seen it yet, here are a few comps that our designer, Logan Liffick, put together:
2021 goal reviewOy, I’m hesitant to even look. All the effort it’s taken to integrate with DigitalOcean and find a new rhythm dominated everybody’s time, leaving precious little to take a crack at Chris’ goals, which were:
- ✅ More SEO focus. I’ll give us a passing grade here. The truth is that Chris and I were already digging our heels into this prior to the acquisition. We replaced the Yoast SEO plugin with RankMath, taking advantage of its in-editor tools to help us learn how to optimize our posts for search results. And to be clear: it’s less about increasing traffic for more sponsorship revenue than it is recognizing that search is the primary way readers like you find us, and making it easier for you to find what you’re looking for. That’s especially true now that we’re backed by DigitalOcean and rely on sponsorships way less than we used to.
- 🚫 Another digital book. Swing and a miss! Well, we never actually swung in the first place, or stepped into the batter’s box for that matter. (Is this how sports analogies work?) Chris published a book of The Greatest CSS Tricks in 2020 and made it a perk of being a paid CSS-Tricks subscriber. The idea was to do another one this year, but we got rid of the paid subscriptions and opened The Greatest CSS Tricks up for everyone to enjoy, free of charge.
- 🚫 More social media experimentation. Nope! But that might be for the best, considering where Twitter is at right this second. We might be forced to experiment in this area next year more out of neccessity than interest. Twitter has always been a drip in the proverbial bucket of CSS-Tricks traffic; so much so that investing in it feels like putting our eggs in the wrong basket, er bucket. I dunno. Part of me just wants to sit on my hands and see how things shake out before deciding on anything new or different.
New year, new goals, right? Allow me to put a bunch of words in the team’s mouth and project what I feel are top priorities for us heading into 2023:
- A smooth site migration. Nothing would make me happier1 than a hiccup-free move to DigitalOcean’s architecture. But c’mon, we all know something always comes up when it’s go time. This site has 7,000+ articles that have been written over 15 years, and there have been 19 versions of the site in that timespan. There are so many custom post types, custom fields, page templates, functionality plugins, integrations, and a database that’s over 6GB to move over and map to an existing system. Good thing we have a team of top-notch developers here to take it on!
- Publish 1-2 new guides. I’d love to aim higher, actually. We went from nine new guides in 2020 to a paltry one new guide in 2021, and another one this past year: Miriam’s Complete Guide to CSS Cascade Layers. I have a list of 10 more that I’d love to write, but think we’ll set the bar super low given our recent track record. I mentioned earlier that I’d love to incorporate Mojtaba’s work in the Almanac into the existing CSS Grid guide. That’s no small amount of work and I’d count it towards the goal if we can pull it off.
- Expand the Almanac. This is my moonshot. I’d love to see more types of documentation in there. We have pseudo-selectors and properties, which is great and always has been. But, geez, think of all the other things we could have in there: functions, at-rules, units, selectors, property values, etc. We’re only scratching the surface of what could possibly go in there! If we get even one of those, I’d be in place-self: heaven.
This is my dream job and I wouldn’t have it without readers like you. I can’t believe it’s been eight years since my very first article was published and that I’m still here, working with learning from the brightest minds in our field. I could ramble (more than I already have) on how much the CSS-Tricks community means to me, but what it really comes down to is… thank you, thank you, thank you. From the bottom of my heart, thank you.
And thanks to all the fine folks here at DigitalOcean who have made a great home for CSS-Tricks. Extra special high-fives to Haley Mills, Sydney Rossman Reich, Bradley Kouchi, Karen Digi, David Berg, Matt Crowley, Logan Liffick, and Kirstyn Kellogg for getting me personally up to speed and making me feel so welcome here. It’s a great place to be.
Forward, we go!
- Well, as happy as a WordPress fanboy like myself can be. ↩️
Thank You (2022 Edition) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
2022 Roundup of Web Research
We’ve started making a tradition of rounding up the latest front-end research at the end of each year. We did it in 2020 and again in 2021. Reports are released throughout the year by a bunch of different companies and organizations researching everything from web design trends to developer skills to popular coding languages and so many other things.
Last year, it seemed the overarching trend was around remote work and its impact on developer productivity and workplace environments. We also saw TailwindCSS continue to explode in usage, dismal accessibility on the top 1 million websites, and a massive growth in API usage, among other findings.
So, what’s in store for this year? Read on to find out.
Table of contents- HTTP Archive Annual State of the Web
- The WebAIM Million 2022
- State of CSS 2022 Survey
- Interop 2022 Dashboard
- Jamstack Community Survey 2022
- 2022 State of Open Source
- StackOverflow 2022 Developer Survey
- GitHub’s 2022 State of the Octoverse
- GitHub Copilot’s impact on developer productivity and happiness
- The Software House State of Frontend 2022
- Sparkbox 2022 Design Systems Survey
- UXTools.co 2022 Design Tools Survey
- 2023 HackerRank Developer Skills Report
- Tower Git Mac Dev Survey 2022
- Developer Nation 2022 Q1 Pulse Report
- Postman 2022 State of the API Report
- CodeinWP WordPress Hosting Survey 2022
- WordPress LMS Websites: A Data Study
- UN E-Government Survey 2022
- LinkedIn 2022 Workplace Learning Report
- UpWork: The Great Work Teardown
- UpWork 2022 Labor Market Trends and Insights
- Reblaze 2022 State of Web Security Survey
- Trend Micro 2022 Midyear Cybersecurity Report
- 1Password: The realities of parenting and growing up online
- The Eclipse Foundation 2022 IoT & Edge Developer Survey
- CampaignMonitor Ultimate Email Marketing Benchmarks for 2022
- Wrapping up
What it is: A study that looks at 8.3 million websites sourced from the Chrome UX Report that analyzes how the sites were made, breaking things up into sections that include page content, user experience, content publishing, and content distribution. The CSS chapter is written by Rachel Andrew (so you know it’s good) and reviewed by folks that include Chris Lilley and CSS-Tricks contributor Jens Oliver Meiert.
What it found: The last two years has shows that CSS continues to contribute to overall page weight. This year was no exception, with CSS weight increasing 7% at the 90th percentile. That’s on par with past years.
What was the most popular class name in 2022? .active. But you also see a bunch of vendor-specific prefixes in the list, like .fa- for Font Awesome, and many for WordPress, such as .wp-, .has-large-font-size, and .has-pale-cyan-blue-background-color (phew!) which I guess is an indication of what the most popular background color is for WordPress sites.
The report also shows pixels as the most commonly used CSS unit for the font-size property. Maybe we’ll see that number trend down next year after Josh Collinsworth published why that’s not a great idea.
There’s so much more to read in here that goes beyond CSS and is worth checking out.
Read Report The WebAIM Million 2022What it is: An evaluation of the accessibility of the top one million as evaulated by the WAVE stand-alone API. That group of sites consists of domains pulled from from the Majestic Millions list, the Alexa Top 1,000,000 web sites, and the DomCop top 10 million domains.
What it found: Well, hey, look at that! The number of distinct accessibility errors has decreased 1.1% since February 2021. That’s modest improvement, but we’re still looking at a grand total of 50,829,406 errors — or an average of 50.8 errors per site — and those are just the ones that could be detected! I think we’d all do well taking time to read Hidde de Vries’s post on how to fix the common accessibility issues outlined in the report.
Read Report State of CSS 2022 SurveyWhat it is: This survey pokes at CSS usage each year, surveying developers on the features they use, as well as their understanding of and satisfaction with them. Co-creator Sacha Greif openly wondered if there is too much CSS in a post he wrote right here on CSS-Tricks and a good part of that was based on the rising number of CSS features in recent years. This year’s survey garnered 8,714 responses from developers around the world.
What it found: Some really neat stuff this year. With all of the new CSS stuff that’s shipped in the past couple of years, there are still plenty of them that have yet to gain traction, whether it’s an awareness thing or the lack of browser support. For example, a whopping 46.7% of folks are only aware of 40% or less of the features covered in the survey. Container queries are a good example of a feature with awareness (58% have heard of it) but little use (12.6% have used it). The :has() selector is a glaring success with 54% knowing about it and 34.8% already using it.
There’s a lot more in here, like CSS-in-JS trends (interest is downward), accessibility features (needs more awareness), and which blogs y’all read (thanks for making CSS-Tricks #1!).
Oh, and don’t miss Lea Verou’s conclusion, which predicts that CSS nesting and color manipulation will rule in 2023. I agree. Nesting would be a game-changer that could put a dent in CSS preprocessor usage. And there are so many new color features today and in the works that are sure to impact the way we define and use colors. But I also suspect that @container becomes a much bigger deal as browser support catches on and we find ourselves writing more container queries where we may have reached for media queries in the past.
See Results Interop 2022 DashboardWhat it is: This is more of a live dashboard than a report. It analyzes data from a fixed point in time, displaying results from the web-platform-tests which are a group of test suites for many web platform specifications that run daily. The idea is that we can see which web features are broadly supported, which ones aren’t, and how browsers rank in terms of supporting them.
What it found: It’s less about what this dashboard has found than what it is currently showing. And right now, Safari is leading the pack as far as supporting what the focus areas are for 2022, which include newer features like Cascade Layers, Container Queries, the <dialog> element, Subgrid, and viewport units. Safari scores 89% in the tests, with Firefox right on its heels at 88% and Chrome/Edge not far behind at 84%.
If you look at the scores for the experimental versions of those browsers, Safari Technology Preview jumps way up to 94% while Firefox and Chrome/Edge sit at 88%. It’s really tough to make the whole “Safari is the new Internet Explorer” point these days, at least when it comes to these focus areas. There are other legitimate criticisms of it for way different reasons that are tied to iOS.
Open Dashboard Jamstack Community Survey 2022What it is: A survey of approximately 7,000 members of the Jamstack community that provides a snapshop of who Jamstack developers are and the sorts of things they’re working on.
What it found: This survey is interesting as heck because it offers a peek into things like job titles and employment on top of Jamstack-specific stuff. For example, four out of five developers are now working remote most of the time and half of those would quit their jobs if they had to return to the office.
Here’s another neat trend: In 2021, 32% of folks referred to themselves as “full-stack developers” in 2021 while 45% called themselves “front-end developers”. That practically swapped in 2022, with 44% of respondents calling themselves “full-stack” and 32% going with “front-end”.
You’ve gotta look at the full set of results to get even more insights on what Jamstack developers are building and how they are building those things. Like, WordPress is still the most widely-used CMS at 59% of respondents (22% of which is headless WordPress), but surprisingly, Notion is quickly gaining traction in the Jamstack CMS space, at 26%. I’ve been interested in Notion as a CMS ever since Chris wrote about it 2020.
See Results 2022 State of Open SourceWhat it is: A survey of 2,660 developers by the Open Source Initiative and OpenLogic that tracks the usage of open source projects and contributions to them. The survey was open for six weeks and attracted responses from 15 countries.
What it found: The Open Source Initiative published their 10 takeaways from the report. Among those? 79% say they sponsor open source organizations (which might be expected from this audience). Deeper in the report, jQuery (31%) is still ranked as the top technology for app development. React (27%) clocks in at second, and Angular (26%) comes in at third.
Download PDF StackOverflow 2022 Developer SurveyWhat it is: A survey of more than 70,000 developers to measure how they learn, which tools they’re using, and what they want in the future.
What it found: I love this survey because it always affirms the amount of time I spend looking things up. 87% of folks spend at least 30 minutes searching for answers to problems, 25% of which spend an hour or more. The survey found that a team of 50 developers spends between 333-651 hours of time looking up answers per week.
Otherwise, JavaScript is the most used language for the tenth year in a row (but Rust is the most loved) and VS Code is the overwhelmingly popular IDE at 74%.
Survey Results GitHub’s 2022 State of the OctoverseWhat it is: Straight from the horse’s mouth: “An exploration of open source software including its impact on the world and companies, plus key trends shaping software development.” It draws on GitHub activity data rather than surveying a group of respondents.
What it found: Whew, 94 million developers used GitHub in 2022! That’s a whole lot more than the 2.8 million who used it in 2012. 20.5 million newbies joined this year alone. Also, there was a 20% year-over-year growth in the number of repos hosted on GitHub, and more than 3.5 billion contributions to GitHub projects over the year. Interestingly, only 20% of all GitHub repos are public, perhaps due to private repos becoming a free feature in 2019.
Nothing new has changed on the languages front. Last year, JavaScript was the most used language and that’s true this year as well. However, TypeScript seems to have slowed down in growth after skyrocketing in popularity last year. I suspected it would jump up a few spots this year, but it’s still in fourth behind Python and Java (which is far from dead).
Read Report GitHub Copilot’s impact on developer productivity and happinessWhat it is: GitHub published a report on GitHub Copilot, its AI-flavored development assistant. Is Copilot making developers’ lives easier? Is it making them more productive? Those are the sorts of things covered in this report, drawing on survey results they published in an academic paper, and external research on development productivity. There’s good qualitiative feedback in there as well.
What it found: Can you guess it? Yep, those who use Copilot feel more productive than those who do not use it. And those who use it complete tasks ~55% faster than those who do not use it for the same tasks. What it sounds like, if I’m reading this right, is that Copilot users enjoy the way it handles all the “fiddly” things for them — like auto-closing brackets.
Dave’s thoughts on Copilot seem to jive with the report’s description of Copilot being like a pair programmer with a calculator attached. Maybe not the best pair programmer in the world, but one in which your mental model shifts from writing code to checking code.
Read report The Software House State of Frontend 2022What it is: A survey of 3,703 developers to “see the real day-to-day perspective from [front-end] professionals of all levels and backgrounds.” What makes this survey a little different is that it also polls 19 invited experts in the field, including — you guessed it — Chris Coyier weighing in on styling tools.
What it found: You know, there’s really more findings here than a mere summary can do justice. This might be the most comprehensive set of results of the whole bunch. There’s so much to grok, from frameworks, hosting, and SSG to browser technologies, code management, and testing. And that only scratches the surface. If nothing else, it’s worth clicking through to the full report just for the analysis from the invited experts.
Read Report Sparkbox 2022 Design Systems SurveyWhat it is: A survey all about design systems that’s focused on adoption, contributions, design, technical debt, and how design systems are used. This year’s results reflect the answers of 219 submissions, down from last year’s 376.
What it found: Last year, the survey found that 40% of folks consider their design systems “successful” or “very” successful. Those figures are less obvious in this year’s survey. But more interesting is what’s included in their systems. Sure, typography, colors, components, and layouts are common to most of them. But it’s the lack of things like developer-ready code (65%), accessibility guidelines (57%), and content guidelines (45%) that might be influencing the finding that only 65% of people who identify as design system subscribers say they get what they need from their systems.
See Results UXTools.co 2022 Design Tools SurveyWhat it is: The sixth edition of a survey that looks at the tooling people use for things like prototyping, UI design, design systems, and user testing. This year received 4,260 submissions.
What it found: First off, we’re dealing with a bunch of designers. 82% have “designer” somewhere in their job title, compared to a mere 6% who call themselves developers. That’s reasonable for a survey that’s all about UX tooling.
So, what tools are they using? Figma by a loooooong mile for UI design. 73% report Figma as their design software of choice, followed by a neck-and-neck race between Adode XD (6%) and Sketch (5%) for a distant second. Figma also leads the pack when it comes to basic UI protoyping and managing design systems.
Do you want to know the top tool for storing, tagging, and organizing research? It’s Notion! Funny how it comes up as both an emerging CMS and a research repository in different surveys.
See Results 2023 HackerRank Developer Skills ReportWhat it is: A survey of HankerRank community members and their development skills, such as the languages they use and their experience with them.
What it found: I don’t know! I tried several times to download the report, but got nothing more than a spinning wheel. The link to the report takes you to a sneak peek with some basic findings, like the top five used languages — Java, Python, SQL, C++, and JavaScript, in that order — make up the overwhelming majority of all reported languages. There’s also findings on the fastest growing languages, which is where TypeScript (182%), PHP (172%), and Go (125%) are dominant. Swift usage fell hard at -42% which is interesting considering the findings in the next survey we’re going to look at.
Read Report Tower Git Mac Dev Survey 2022What it is: A survey of 2,506 developers (down from last year’s 4,072) working on the MacOS platform with the goal of understanding the profile of this specific developer niche.
What it found: Last year’s takeaway was the age of this crowd trending younger, suggesting a growth in Mac-related development. And lots of them really wanted to learn Swift. What changed? Not a whole lot! Most developer are still in the 30-44 age range (40.9%) even though that’s significantly down from 54.8% last year. And the largest age group (19.5%) is in the 35-39 range. They still work with JavaScript most (52.7%) and still want to learn Swift the most (28.2%).
See Results Developer Nation 2022 Q1 Pulse ReportWhat it is: A report is based on a global online developer survey designed, produced, and carried out by SlashData over ten weeks between December 2021 and February 2022 to measure developer trends, technology preferences, and emerging technology patterns.
What it found: I like that this report breaks down its demographics by gender. And while the result is unsurprising — there are way more men (81%) than women (17%) — it’s still a confirmation of the almost tangible dismal gender equality in the development industry as a whole.
Wanna know this survey’s top five programming languages? It’s exactly the same as HackerRank’s top five, with one exception: C# knocked JavaScript off the list. I also find it interesting that the top emerging area of interest for this group is artificial intelligence software, beating out augmented reality, robotics, cryptocurrency, and blockchain. Maybe some of these folks are the ones influencing GitHub’s Copilot research findings?
Read Report Postman 2022 State of the API ReportWhat it is: A survey of more than 37,000 developers (up from 28,000 last year and 13,500 in 2020!) that measures who is developing with APIs, what sort of work they’re doing with them, and how APIs are evolving.
What it found: Last year, I reported this:
67% of developers say they’ve adopted an API-first philosophy and 94% say they believe their companies will either invest more or the same in APIs in the next year. We’ll see when those results roll in next year!
The same data point this year says that number is down to 89% — and with a larger pool of survey participants. That said, Postman API requests skyrocketed from 855 million last year to 1.13 billion this year. Wow. I’d say last year’s prediction that more companies would investment in API usage this year is spot on.
The most popular APIs? That group includes known entities like Salesforce and Twitter, but welcomes Notion to the list — it’s really been a banner year for Notion according to many of the surveys in this roundup.
Get Report CodeinWP WordPress Hosting Survey 2022What it is: A survey all about WordPress hosting that polls people who read the CodeinWP blog. They received 3,400 submissions this year. They’ve apparently been doing this survey since 2016 but it’s slipped under my radar until this year.
What it found: GoDaddy is the hosting provider of choice for this group, which was the story in 2019 and 2020 as well. But it only represents 11.8% of survey participants. The market is pretty crowded with Bluehost (8.4%), Hostinger (4.8%), and HostGator (3.4%) trailing behind. LOLzzz for GoDaddy also falling dead last in hosting satisfaction with 6.3/10 satisfaction rate. WP Engine got the top rating score (9.2/10) but that’s based on just 21 survey participants, compared to GoDaddy’s 377. Plus, the survey notes that many specified “WordPress” as their host… which could either mean they use WordPress.com or are simply confused between WordPress.com and a self-hosted WordPress site. 🤷♂️
See Results WordPress LMS Websites: A Data StudyWhat it is: Let’s look at another WordPress-centric survey while we’re at it. This one is run by a group called Sell Courses Online, which is a dead giveaway that it’s focused on learning management systems (LMS) in the WordPress ecosystem.
What it found: I admit I’m super interested in this report because I teach web development in higher education and have played with a bunch of LMSs. WordPress is ripe software for for it, too, with quite a few plugin options. It’s super affordable as well, with most folks (41.3%) spending less than $50/month on their tech stack, and 76.2% spending less than $250. Most of those low-spend sites rely on a freemium-based LMS model.
And what’s included in that stack? 65.3% rely on WooCommerce for selling courses, 57.5% use Elementor as a page builder, 19% use the Astra theme (while 66% specify others), and 13.5% use Paid Memberships Pro for user accounts.
Hey, what about the actual LMS functionality? LearnDash is is the most popular LMS plugin with 34%, followed by LearnPress (31%) and Tutor LMS (19%). I’ve worked with LearnDash and love it, especially the number of add-ons to extend the functionality with more features as needed.
View Research UN E-Government Survey 2022What it is: It’s funny, but I have a degree in Economics that I clearly haven’t used in my professional career, and there’s a bunch of stuff in here that’s way over my head. What it boils down to, if I’m understanding correctly, is that this report measures the online development of governments across United Nations member states, drawing on a composite of three different indices.
Has the United States progressed in its digital infrastructure and strategies? That’s the sort of thing this report looks at, taking in factors like what online services a country provides, how it approaches cybersecurity, efforts to increase digital proficiencies, and even how technology has been used to address crises like the COVID-19 pandemic.
The first survey was published in 2001. This 2022 survey is the eleventh edition of this biennial publication.
What it found: Honestly, you’d do better reading the press release (PDF) than relying on my uneducated insights. But at a super high level, Denmark, Finland and the Republic of Korea lead the 2022 digital government rankings, “scoring the highest when it comes to the scope and quality of online services, status of telecommunication infrastructure and existing human capacity.”
See Results LinkedIn 2022 Workplace Learning ReportWhat it is: The name of the report sorta says it all — LinkedIn looks at the state of the professional learning landscape in workplaces. This is the sixth edition, surveying 1,444 learning and development professionals (L&D), and 610 learners in November 2021.
A lot of this year’s report is written around the COVID-19 pandemic’s impact on learning in the workplace, like how learning has been affected by layoffs and remote work arrangements.
What it found: Learning continues despite The Great Reshuffle/Great Resignation or whatever you want to call the relatively new trend of quitting jobs and changing careers. For example, 46% of L&D professionals say there is a wider technological skill gap on their teams, and 49% say execs are concerned that employees do not have the right skills to meet business strategies. That suggests the post-pandemic technological landscape has created higher expectations as far as employees having relevant technical skills, particularly when it comes to what’s needed for successful remote work.
That, in turn, has led to a rise in demand for workplace learning programs and profressionals. L&D professionals are in higher demand and make more money than they did before. And only 8% expect their L&D budget to decrease in the coming year.
What sorts of learning programs have top priority? Diversity, equity, and inclusion (45%), leading through change programs (42%), in-person training (41%, up from 25%!), upskilling and reskilling (41%), and digital fluency (30%). A lot of soft skills in there!
View Report UpWork: The Great Work TeardownWhat it is: While we’re on the topic of changing workplace environments, let’s look at this one that investigates the workplace trends that are changing perspectives on when, where, and how people work — and how businesses are adapting to those changing perspectives.
What it found: The stats are super interesting, but I couldn’t find any information on the methodology it used to get them. Like 50% of businesses have reported higher turnover compared to pre-pandemic times, 38% plan to spend more on independent remote freelancers, and 37% are fully remote today with 28% expecting to go fully remote in a year. What’s going to happen to all those empty office buildings?!
On the employee side of things, 61% say they are more productive when they work remote. 45% of business also report an increase in productivity as a result of remote work and a whopping 63% reduction in unscheduled work absences.
There are other interesting stats on how other things are changing, like traditional work hours, where people choose to work, and the perception of workplace culture.
Get Report UpWork 2022 Labor Market Trends and InsightsWhat it is: Another one from UpWork! This time it’s looking at the overall labor market. And there’s a documented methodology this time, saying that numbers are based on survey results of 1,000 hiring professionals from a third-party as well as findings from a separate study from a separate firm that surveyed 6,000 working professionals.
What it found: Well, UpWork’s “Great Work Teardown” report found that there’s big growth in business relying on remote freelancers. This report confirms that 78% of hiring pros saying they’ve used remote freelancers and 52% saying they are using more of them today than they have in previous years.
Get this: 60% of managers at mid-sized companies report higher turnover since pre-pandemic levels, while only 25% of small companies report the same. And roughly 45% of all hiring managers say they plan to combat turnover by offering more learning programs, confirming LinkedIn’s workplace learning report.
And, hey, if you’re looking for a higher salary or more perks, this might be the time to to strike because around 50% of managers are considering higher salaries and bigger benefit packages to retain staff.
See Trends Reblaze 2022 State of Web Security SurveyWhat it is: This survey is new to the collection! It asked 300 web security pros what they consider to be their biggest online threats and how they plan on defending against them.
What it found: The most common attacks were DDoS, with half of the survey’s participants saying they’ve dealt with them in the past year. Next up is SQL injections (38%) and ransomware (29%), where ransomware is considered the most severe threat. (The report also cites a U.S. Treasury finding that U.S. firms paid out $590 million in ransomware attacks in the first half of 2021 alone. Geez.)
Also neat: 90% of participants say they are using a public cloud, making cloud-based security more of a thing. (AWS tops the list at 67%. DigitalOcean (4%), the home for CSS-Tricks, is sandwiched between Oracle (7%) and IBM (3%) as an interesting aside.)
API security is tops as far as priority goes. With Postman’s State of the API report showing a year-over-year increase in API requests that goes from 855 million last year to 1.13 billion this year, it’s clear why that is.
See Results(Linking directly to the PDF to save you the registration effort.)
Trend Micro 2022 Midyear Cybersecurity ReportWhat it is: Let’s keep talking cybersecurity. This report polls 6,297 IT security decision-makers from 29 countries about their thoughts on the cybersecurity risks they face.
What it found: A good chunk of folks (62%) say they have blindspots in their defense strategies and 43% belive that that their exposure to threats is out of control. That’s in line with the Reblaze survey above that reported 50% of folks saying they have no certainty as far as how many bots account for overall traffic. This report notes that 37% of participants cite cloud assets as the area they have the least insight into.
The report gets into a bunch of specific attacks that I had no idea were even a thing. It’s unnerving how attacks seem to get smarter and smarter each year while the businesses continue to increase their exposure to them. This report provides a lot of excellent detail on those threats, including a section devoted to cybersecurity efforts in the Russia-Ukraine conflict.
Get Report 1Password: The realities of parenting and growing up onlineWhat it is: Let’s heap more on the cybersecurity research pile will this report from the folks behind the 1Password app. This one hits pretty close to home for me because it looks at parenting in the always-online era, which is something pinned to the back of my mind since I have two young daughters who love their screens.
1Password teamed up with Malwarebytes to produce this report, which is based on a survey of 1,000 parents and 1,000 children that were born between 1997-2009 that was prepared by another firm, Method Research. The data is fresh having been collected in August this year.
What it found: A bunch of stats I wish I could unsee. Like 74% of parents think they’re keeping their kids safe, while only 51% agree. And 74% of kids have ways to workaround being monitored by their parents (where 9% claim to have a secret device their parents don’t know about). Cyberbullying is the top concern for both parents (73%) and kids (66%).
Parents also need to be more responsible. 73% of kids wish their parents would ask for permission to post photos of them while only 34% of parents actually ask (and — eek! — 39% don’t believe they need to ask permission). The importance here is that 11% of kids say they’ve been stalked or bullied because of something posted by their parents, and 12% report being harmed in some way, whether its hacked accounts, stolen identities, or tarnished credit cores.
Download PDF The Eclipse Foundation 2022 IoT & Edge Developer SurveyWhat it is: A survey of 910 global developers, committers, architects, and decision-makers that took place between April and June 2022 to spot trends in the Internet of Things (IoT) space. You know, like that smart fridge in your kitchen and voice-controlled curtains that shade your living room. That and more serious stuff like trends in artificial intelligence and edge computing.
What it found: Last year, I called Microsoft’s IoT findings “mostly nice trivia for cocktail chatter.” This report is a lot more granular and is probably most helpful for those working in the space, as there’s so much information on the stacks that developers use and overarching concerns about them. I mean, I don’t particularly care that “64-bit ARM architectures are gaining ground with gateway and edge node suppliers.” But I bet that’s super important to some of you reading this.
Download PDF(Linking directly to the PDF to save you the registration effort.)
CampaignMonitor Ultimate Email Marketing Benchmarks for 2022What it is: A study of benchmarks related to email marketing based on 100 billion emails delivered on CampaignMonitor’s platform in 2021. It looks at things like open and click rates, and breaks them down by industry to help folks get the most out of their email marketing campaigns and know how to gauge success.
What it found: It’s probably better for you to see their table of results by industry rather than having me regurgitate the results of all 19 industries they identified. But on a global level, a 21.5% open rate is quite average across all industries, as is a 2.3% click-through rate. It appears Monday produces the highest open rate (22% on average) while Sunday produces the lowest (20.3%), so not a whole lot of variance there. Same deal with click-through rates, where Tuesday is highest (2.4%) and Saturday and Sunday share the lowest rate (2.1%). Again, not a lot of difference but it could be helpful knowing this stuff if you’re trying to milk every last drop out of a campaign.
See Benchmarks Wrapping upWe looked at 27 different reports on front-end and front-end-adjacent topics! That’s more than the 25 we covered last year and the fitting number of 20 we looked at in 2020.
If there’s one BIG takeaway from all these takeaways, it’s to remember this is all in good fun. Many of the studies lack the scientific methods we’d want to base decisions on, and the sample sizes are far too small to accurately reflect reality. But they sure are interesting, right?!
Some reports are annual, some are one-off, and others seemingly happen whenever the heck they wanna do it. So, if I missed any from previous years, it’s probably because they aren’t annual or just aren’t available as I’m writing this. As always, let me know if there’s a report I missed and I’ll try to work it in.
There are more reports on the way! In fact, you can take the 2022 State of JavaScript survey and the 2022 WordPress Annual Survey as this is being written. Have at ’em!
2022 Roundup of Web Research originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Help choose the syntax for CSS Nesting
CSS Nesting is making the rounds yet again. Remember earlier this year when Adam and Miriam put three syntax options up for a vote? Those results were tallied and it wasn’t even even close.
Now there’s another chance to speak into the future of nesting, this time over at the WebKit blog. The results from the Adam and Miriam’s survey sparked further discussion and two more ideas were added to the mix. This new survey lets you choose from all five options.
Jen Simmons has put together a thorough outline of those options, including a refresher on nesting, details on how we arrived at the five options, and tons of examples that show the options in various use cases. Let’s return the favor of all the hard work that’s being done here by taking this quick one-question survey.
To Shared Link — Permalink on CSS-Tricks
Help choose the syntax for CSS Nesting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
WordPress Playground: Running WordPress in the Browser
Being able to quickly spin up a WordPress instance has been the strength of WordPress ever since its famous “five-minute install”. Upload a few files, configure a few settings, and you’re off.
The friction of uploading files has gotten a lot easier, thanks to plenty of “one-click” install options many hosts offer (including DigitalOcean and Cloudways).
Some companies have tried to abstract the process even more, using the multi-site features of WordPress to fire up disposable instances for testing and demos. WordPress Sandbox and WP Sandbox come to mind. Scaling can be an issue here, as instances run on the same install adding lag to the entire network. I worked on a headless WordPress project that did this in the background for users, and I recall the incredibly long wait it would take users to create a new account as the number of sites in the network piled up.
Enter WordPress Playground. It runs entirely in the browser which is mindblowing to me as a long-time WordPress user. If you’re having a hard time wrapping your head around how it all works like I did, that link to the overview spells it out nicely:
- PHP runs as a WebAssembly binary
- MySQL is replaced for SQLite via a WordPress plugin
- Web server is implemented with the Service Worker API
Dang, that’s cool. The move to SQLite is especially interesting, as it could bring huge performance gains to many sites that might not need the full heft of WordPress — a “WordPress Lite” as Chris recently described it in a different context. In fact, that work is already happening in the experimental WordPress performance plugin.
The evolution to a light, frictionless WordPress is a fun space to watch. I imagine there’s a good chunk of existing WordPress sites that stand to benefit from a slimmed-down CMS. The demo offers a glimpse at what an onboarding experience for that sort of thing could look like.
Select a theme, choose your features, and go!WordPress Playground: Running WordPress in the Browser originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSS Infinite 3D Sliders
In this series, we’ve been making image sliders with nothing but HTML and CSS. The idea is that we can use the same markup but different CSS to get wildly different results, no matter how many images we toss in. We started with a circular slider that rotates infinitely, sort of like a fidget spinner that holds images. Then we made one that flips through a stack of photos.
This time around, we’re diving into the third dimension. It’s going to look tough at first, but lots of the code we’re looking at is exactly what we used in the first two articles in this series, with some modifications. So, if you’re just now getting into the series, I’d suggest checking out the others for context on the concepts we’re using here.
CSS Sliders series- Circular Rotating Image Slider
- Flipping Through Polaroid Images
- Infinite 3D Sliders (you are here!)
This is what we’re aiming for:
CodePen Embed FallbackAt first glance, it looks like we have a rotating cube with four images. But in reality, we’re dealing with six images in total. Here is the slider from a different angle:
Now that we have a good visual for how the images are arranged, let’s dissect the code to see how we get there.
The basic setupSame HTML as the rest of the sliders we’ve used for the other sliders:
<div class="gallery"> <img src="" alt=""> <img src="" alt=""> <img src="" alt=""> <img src="" alt=""> <img src="" alt=""> </div>And once again, we’re using CSS Grid to place the images in a stack, one on top of another:
.gallery { display: grid; } .gallery > img { grid-area: 1 / 1; width: 160px; aspect-ratio: 1; object-fit: cover; } The animationThe logic for this slider is very similar to the circular slider from the first article. In fact, if you check the video above again, you can see that the images are placed in a way that creates a polygon. After a full rotation, it returns to the first image.
We relied on the CSS transform-origin and animation-delay properties for that first slider. The same animation is applied to all of the image elements, which rotate around the same point. Then, by using different delays, we correctly place all the images around a big circle.
The implementation will be a bit different for our 3D slider. Using transform-origin won’t work here because we’re working in 3D, so we will use transform instead to correctly place all the images, then rotate the container.
We’re reaching for Sass again so we can loop through the number of images and apply our transforms:
@for $i from 1 to ($n + 1) { .gallery > img:nth-child(#{$i}) { transform: rotate(#{360*($i - 1) / $n}deg) /* 1 */ translateY(50% / math.tan(180deg / $n)) /* 2 */ rotateX(90deg); /* 3 */ } }You might be wondering why we’re jumping straight into Sass. We started with a fixed number of images using vanilla CSS in the other articles before generalizing the code with Sass to account for any number (N) of images. Well, I think you get the idea now and we can cut out all that discovery work to get to the real implementation.
The transform property is taking three values, which I’ve illustrated here:
We first rotate all the images above each other. The angle of rotation depends on the number of images. For N images, we have an increment equal to 360deg/N. Then we translate all of the images by the same amount in a way that makes their center points meet on the sides.
There’s some boring geometry that helps explain how all this works, but the distance is equal to 50%/tan(180deg/N). We dealt with a similar equation when making the circular slider ( transform-origin: 50% 50%/sin(180deg/N) ).
Finally, we rotate the images around the x-axis by 90deg to get the arrangement we want. Here is a video that illustrates what the last rotation is doing:
Now all we have to do is to rotate the whole container to create our infinite slider.
.gallery { transform-style: preserve-3d; --_t: perspective(280px) rotateX(-90deg); animation: r 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite; } @keyframes r { 0%, 3% {transform: var(--_t) rotate(0deg); } @for $i from 1 to $n { #{($i/$n)*100 - 2}%, #{($i/$n)*100 + 3}% { transform: var(--_t) rotate(#{($i / $n) * -360}deg); } } 98%, 100% { transform: var(--_t) rotate(-360deg); } }That code might be hard to understand, so let’s actually step back a moment and revisit the animation we made for the circular slider. This is what we wrote in that first article:
.gallery { animation: m 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite; } @keyframes m { 0%, 3% { transform: rotate(0); } @for $i from 1 to $n { #{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}% { transform: rotate(#{($i / $n) * -360}deg); } } 98%, 100% { transform: rotate(-360deg); } }The keyframes are almost identical. We have the same percentage values, the same loop, and the same rotation.
Why are both the same? Because their logic is the same. In both cases, the images are arranged around a circular shape and we need to rotate the whole thing to show each image. That’s how I was able to copy the keyframes from the circular slider and use that same code for our 3D slider. The only difference is that we need to rotate the container by -90deg along the x-axis to see the images since we have already rotated them by 90deg on the same axis. Then we add a touch of perspective to get the 3D effect.
That’s it! Our slider is done. Here is the full demo again. All you have to do is to add as many images as you want and update one variable to get it going.
CodePen Embed Fallback Vertical 3D sliderSince we are playing in the 3D space, why not make a vertical version of the previous slider? The last one rotates along the z-axis, but we can also move along the x-axis if we want.
CodePen Embed FallbackIf you compare the code for both versions of this slider, you might not immediately spot the difference because it’s only one character! I replaced rotate() with rotateX() inside the keyframes and the image transform. That’s it!
It should be noted that rotate() is equivalent to rotateZ(), so by changing the axis from Z to X we transform the slider from the horizontal version into the vertical one.
Cube sliderWe cannot talk about 3D in CSS without talking about cubes. And yes, that means we are going to make another version of the slider.
The idea behind this version of the slider is to create an actual cube shape with the images and rotate the full thing in around the different axis. Since it’s a cube, we’re dealing with six faces. We’ll use six images, one for each face of the cube. So, no Sass but back to vanilla CSS.
CodePen Embed FallbackThat animation is a little overwhelming, right? Where do you even start?
We have six faces, so we need to perform at least six rotations so that each image gets a turn. Well, actually, we need five rotations — the last one brings us back to the first image face. If you go grab a Rubik’s Cube — or some other cube-shaped object like dice — and rotate it with your hand, you’ll have a good idea of what we’re doing.
.gallery { --s: 250px; /* the size */ transform-style: preserve-3d; --_p: perspective(calc(2.5*var(--s))); animation: r 9s infinite cubic-bezier(.5, -0.5, .5, 1.5); } @keyframes r { 0%, 3% { transform: var(--_p); } 14%, 19% { transform: var(--_p) rotateX(90deg); } 31%, 36% { transform: var(--_p) rotateX(90deg) rotateZ(90deg); } 47%, 52% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); } 64%, 69% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg); } 81%, 86% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg); } 97%, 100%{ transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); } }The transform property starts with zero rotations and, on each state, we append a new rotation on a specific axis until we reach six rotations. Then we are back to the first image.
Let’s not forget the placement of our images. Each one is applied to a face of the cube using transform:
.gallery img { grid-area: 1 / 1; width: var(--s); aspect-ratio: 1; object-fit: cover; transform: var(--_t,) translateZ(calc(var(--s) / 2)); } .gallery img:nth-child(2) { --_t: rotateX(-90deg); } .gallery img:nth-child(3) { --_t: rotateY( 90deg) rotate(-90deg); } .gallery img:nth-child(4) { --_t: rotateX(180deg) rotate( 90deg); } .gallery img:nth-child(5) { --_t: rotateX( 90deg) rotate( 90deg); } .gallery img:nth-child(6) { --_t: rotateY(-90deg); }You are probably thinking there is weird complex logic behind the values I’m using there, right? Well, no. All I did was open DevTools and play with different rotation values for each image until I got it right. It may sound stupid but, hey, it works — especially since we have a fixed number of images and we are not looking for something that supports N images.
In fact, forget the values I’m using and try to do the placement on your own as an exercise. Start with all the images stacked on top of each other, open the DevTools, and go! You will probably end up with different code and that’s totally fine. There can be different ways to position the images.
What’s the trick with the comma inside the var()? Is it a typo?
It’s not a typo so don’t remove it! If you do remove it, you will notice that it affects the placement of the first image. You can see that in my code I defined --_t for all the images except the first one because I only need a translation for it. That comma makes the variable fall back to a null value. Without the comma, we won’t have a fallback and the whole value will be invalid.
From the specification:
Note: That is, var(--a,) is a valid function, specifying that if the --a custom property is invalid or missing, the var()` should be replaced with nothing.
Random cube sliderA little bit of randomness can be a nice enhancement for this sort of animation. So, rather than rotate the cube in sequential order, we can roll the dice so to speak, and let the cube roll however it will.
CodePen Embed FallbackCool right? I don’t know about you, but I like this version better! It’s more interesting and the transitions are satisfying to watch. And guess what? You can play with the values to create your own random cube slider!
The logic is actual not random at all — it just appears that way. You define a transform on each keyframe that allows you to show one face and… well, that’s really it! You can pick any order you want.
@keyframes r { 0%, 3% { transform: var(--_p) rotate3d( 0, 0, 0, 0deg); } 14%,19% { transform: var(--_p) rotate3d(-1, 1, 0,180deg); } 31%,36% { transform: var(--_p) rotate3d( 0,-1, 0, 90deg); } 47%,52% { transform: var(--_p) rotate3d( 1, 0, 0, 90deg); } 64%,69% { transform: var(--_p) rotate3d( 1, 0, 0,-90deg); } 81%,86% { transform: var(--_p) rotate3d( 0, 1, 0, 90deg); } 97%,100% { transform: var(--_p) rotate3d( 0, 0, 0, 0deg); } }I am using rotate3d() this time but am still relying on DevTools to find the values that feel “right” to me. Don’t try to find a relationship between the keyframes because there simply isn’t one. I’m defining separate transforms and then watching the “random” result. Make sure the first image is the first and last frames, respectively, and show a different image on each of the other frames.
You are not obligated to use a rotate3d() transform as I did. You can also chain different rotations like we did in the previous example. Play around and see what you can come up with! I will be waiting for you to share your version with me in the comments section!
Wrapping upI hope you enjoyed this little series. We built some fun (and funny) sliders while learning a lot about all kinds of CSS concepts along the way — from grid placement and stacking order, to animation delays and transforms. We even got to play with a dash of Sass to loop through an array of elements.
And we did it all with the exact same HTML for each and every slider we made. How cool is that? CSS is dang powerful and capable of accomplishing so much without the aid of JavaScript.
CSS Infinite 3D Sliders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
So, you’d like to animate the display property
The CSS Working Group gave that a thumbs-up a couple weeks ago. The super-duper conceptual proposal being that we can animate or transition from, say, display: block to display: none.
It’s a bit of a brain-twister to reason about because setting display: none on an element cancels animations. And adding it restarts animations. Per the spec:
Setting the display property to none will terminate any running animation applied to the element and its descendants. If an element has a display of none, updating display to a value other than none will start all animations applied to the element by the animation-name property, as well as all animations applied to descendants with display other than none.
That circular behavior is what makes the concept seemingly dead on arrival. But if @keyframes supported any display value other than none, then there’s no way for none to cancel or restart things. That gives non-none values priority, allowing none to do its thing only after the animation or transition has completed.
Miriam’s toot (this is what we’re really calling these, right?) explains how this might work:
We’re not exactly interpolating between, say, block and none, but allowing block to stay intact until the time things stop moving and it’s safe to apply none. These are keywords, so there are no explicit values between the two. As such, this remains a discrete animation. We’re toggling between two values once that animation is complete.
This is the Robert Flack’s example pulled straight from the discussion:
@keyframes slideaway { from { display: block; } to { transform: translateY(40px); opacity: 0;} } .hide { animation: slideaway 200ms; display: none; }This is a helpful example because it shows how the first frame sets the element to display: block, which is given priority over the underlying display: none as a non-none value. That allows the animation to run and finish without none cancelling or resetting it in the process since it only resolves after the animation.
This is the example Miriam referenced on Mastodon:
.hide { transition: opacity 200ms, display 200ms; display: none; opacity: 0; }We’re dealing with a transition this time. The underlying display value is set to none before anything happens, so it’s completely out of the document flow. Now, if we were to transition this on hover, maybe like this:
.hide:hover { display: block; opacity: 1; }…then the element should theoretically fade in at 200ms. Again, we’re toggling between display values, but block is given priority so the transition isn’t cancelled up front and is actually applied after opacity finishes its transition.
At least that’s how my mind is reading into it. I’m glad there are super smart people thinking these things through because I imagine there’s a ton to sort out. Like, what happens if multiple animations are assigned to an element — will none reset or cancel any of those? I’m sure everything from infinite animations, reversed directions, and all sorts of other things will be addressed in time.
But what a super cool first step!
So, you’d like to animate the display property originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Some Links on AI-Related Stuff
Every so often, I find that the links I save to read later fall into natural groups or patterns that reveal common threads of interest. The past couple of weeks have produced a lot of thoughts about ChatGPT, an AI-powered interface that responds to requests in a chat-like exchange. Sorta like a “Hey Siri” request, but in a Discord channel.
ChatGPT is just one of several AI-flavored tech, including GitHub’s CoPilot (writing code) and Dall-E (generative images and art).
Is it the end of human development? A new and exciting way to produce art? Just cocktail party conversation fodder? There are lots of opinions…
- A Conversation With ChatGPT (Matthias Ott) — Matthias has a conversation with ChatGPT about typography that delves into deeply theoretical thoughts on design process. My favorite is in response to whether designers should learn to code: “Ultimately, whether or not designers should learn to code is a decision that each individual designer must make for themselves, based on their own goals and circumstances. Some designers may benefit from learning to code, while others may be better served by focusing on design principles and concepts.”
- They were supposed to replace the creative jobs last (Dave Rupert)— “As interesting a future this creates, I’m a member of an old caste of people that still believes massive gains don’t come without realized costs; or more explicitly, electricity isn’t the only cost. What if the cost we’re paying is our perception of reality itself? It’s increasingly likely that the next thing you read or watch is the product of a content extruder.”
- I just used ChatGPT to help with a complicated equation. — A reddit user used ChatGPT to write a complex equation in Notion. There were a couple hiccups, but it worked in the end.
- ChatGPT Creates a Working WordPress Plugin – On the First Try (WP Tavern) — Sarah Gooding reporting on a ChatGPT experiment where Johnathon Williams was able to spit out a fully-functional WordPress plugin with a simple chat command. This is the sort of thing that both terrifies me but also blows my mind-hole.
- ChatGPT Is a Smart Computer’s Impression of a Know-It-All (Pixel Envy) — Nick Heer points to an article on The Atlantic about ChatGPT that opens with three paragraphs written by ChatGPT. It’s crazy that it comes off as naturally as it does, even if it smells slightly fishy at first.
- Use of ChatGPT1 generated text for content on Stack Overflow is temporarily banned. (Stack Overflow) — A mild dose of #HotDrama as far as Stack Overflow users posting ChatGPT-produced code as answers.
- Midjourney vs. human illustrators: has AI already won? (Evil Martians) — I love the experiment in this post because it’s a clear example that AI doesn’t *just* work. In its current state, at best, AI is a junior designer when put to the task of creating an image: “After two and a half hours of back and forth with the AI, I was completely exhausted and decided to just upscale the most promising result.” A bonus is that the post concludes with a list of situations where AI might realistically help the team with future work — and it ain’t an entire person’s job.
- Quick Thoughts on AI (Collaborative Fund) — Ha! Crazy to see a chart comparing how fast ChatGPT reached one million users to other popular services. It took Facebook 10 months, but only five days for ChatGPT.
Dall-E, I want a photo of a developer sitting at a desk with his head exploding while having a chat conversation on a desktop computer with an artificial intelligence algorithm.
Not bad, not bad.
Some Links on AI-Related Stuff originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Unchain My Inaccessibly-Labelled Heart
Suzy Naschansky from the HTMHell Advent Calendar:
<h2 id="article1-heading">All About Dragons</h2> <p>I like dragons. Blah blah blah blah blah.</p> <p> <a id="article1-read-more" aria-labelledby="article1-read-more article1-heading">Read more</a> </p>See that aria-labelledby attribute? It chains two IDs from the markup, one for the heading (#article1-heading) and one for the link (#article1-read-more). What happens there is a screenreader will replace the existing semantic label between the link tags and use the content from both elements and announce them together as a single string of text:
Read more All About DragonsI’m always sheepish when realizing there’s something I think I should know but don’t. This is definitely one of those cases and I’m thankful as all heck that Suzy shared it.
I was actually in a situation just recently where I could’ve should’ve done this. I always try to avoid a bunch of “Read more” links on the same page but coming up with different flavors of the same thing is tough when you’re working with something like a loop of 15 posts (even though there are resources to help). And if we need to keep labels short for aesthetic reasons — design requirements and whatnot — it’s even more challenging. The aria-labelledby attribute gives me exactly what I want: consistent visual labels and more contextual announcements for assistive tech.
And this is only a thing when the text you want to use for the accessible label already exists on the page. Otherwise, you’d want to go with aria-label and with the caveat that it’s purely for interactive elements that are unable to label things accessibly with semantic HTML.
If you are working in a CMS like WordPress (which I am), you might need to do a little extra work. Like when I drop a Button block on the page, these are the options I have to work with:
Some nice options in there, but nothing to do with accessible labelling. If you’re wondering what’s buried in that Advanced panel:
Close, but no cigar.Instead, you’ll need to edit the button in HTML mode:
But before doing that, you gotta add an ID to the heading you want to use. The Heading block has the same Advanced panel setting for adding an anchor, which’ll inject an ID on the element:
Then you can go edit the Button block in HTML mode and add the accessible-labels ID as well as an ID for the button itself. This is an example of the edited markup:
<div class="wp-block-buttons"> <!-- wp:button --> <div class="wp-block-button"> <a id="read-more-button" aria-labelledby="read-more-button heading-accessible-labels" class="wp-block-button__link wp-element-button" href="https://webaim.org/projects/million/"> Read Report </a> </div> <!-- /wp:button --> </div>Great! But WordPress just ain’t cool with that:
You can try to resolve the issue:
Le sigh. The Button block has to be converted to a Custom HTML block. Kinda defeats the whole visual editing thing that WordPress is so good at. I did a super quick search for a plugin that might add ARIA labelling options to certain blocks, but came up short. Seems like a ripe opportunity to make one or submit PRs for the blocks that could use those options.
Unchain My Inaccessibly-Labelled Heart originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Holiday Snowtacular 2022
We’ve got ourselves a real holiday treat! Join host Alex Trost from the Frontend Horse community for the Holiday Snowtacular 2022 this Friday, December 16.
There’s a lineup of 12 awesome speakers — including Chris Coyier, Cassidy Williams, Kevin Powell, and Angie Jones — each discussing various front-end and web dev topics. It’s like the 12 days of Christmas, but wrapped up in a four-hour session for web nerds like us.
It’s a real good cause, too. The event is free, but includes fundraising Doctors Without Borders with a goal of reaching $20,000. You can donate here any time and anything you give will be matched by the event’s sponors. So, come for the front-end fun and help a great cause in the process.
To Shared Link — Permalink on CSS-Tricks
Holiday Snowtacular 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
A Few Times Container Size Queries Would Have Helped Me Out
CSS Container Queries are still gaining traction and many of us are getting our hands wet with them, even if it’s for little experiments or whatnot. They’ve got great, but not quite full, browser support — enough to justify using them in some projects, but maybe not to the extent where we might be tempted to start replacing media queries from past projects with shiny new container size queries.
They sure are handy though! In fact, I’ve already run into a few situations where I really wanted to reach for them but just couldn’t overcome the support requirements. If I had been able to use them, this is how it would have looked in those situations.
All of the following demos will be best viewed in Chrome or Safari at the time of this writing. Firefox plans to ship support in Version 109.
Case 1: Card gridYou kind of had to expect this one, right? It’s such a common pattern that all of us seem to run into it at some point. But the fact is that container size queries would have been a huge time-saver for me with a better outcome had I been able to use them over standard media queries.
Let’s say you’ve been tasked with building this card grid with the requirement that each card needs to keep it’s 1:1 aspect ratio:
It’s tougher than it looks! The problem is that sizing a component’s contents on the viewport’s width leaves you at the mercy of how the component responds to the viewport — as well the way any other ancestor containers respond to it. If, for example, you want the font size of a card heading to reduce when the card hits a certain inline size there’s no reliable way to do it.
You could set the font size in vw units, I suppose, but the component is still tied to the browser’s viewport width. And that can cause problems when the card grid is used other in contexts that may not have the same breakpoints.
In my real-world project, I landed on a JavaScript approach that would:
- Listen for a resize event.
- Calculate the width of each card.
- Add an inline font size to each card based on its width.
- Style everything inside using em units.
Seems like a lot of work, right? But it is a stable solution to get the required scaling across different screen sizes in different contexts.
Container queries would have been so much better because they provide us with container query units, such as the cqw unit. You probably already get it, but 1cqw is equal to 1% of a container’s width. We also have the cqi unit that’s a measure of a container’s inline width, and cqb for a container’s block width. So, if we have a card container that is 500px wide, a 50cqw value computes to 250px.
If I had been able to use container queries in my card grid, I could have set up the .card component as a container:
.card { container: card / size; }Then I could have set an inner wrapper with padding that scales at 10% of the .card‘s width using the cqw unit:
.card__inner { padding: 10cqw; }That’s a nice way to scale the spacing between the card’s edges and its contents consistently no matter where the card is used at any given viewport width. No media queries required!
Another idea? Use cqw units for the font size of the inner contents, then apply padding in em units:
.card__inner { font-size: 5cqw; padding: 2em; }5cqw is an arbitrary value — just one that I settled on. That padding is still equal to 10cqw since the em unit is relative to the .card__inner font size!
Did you catch that? The 2em is relative to the 5cqw font size that is set on the same container. Containers work different than what we’re used to, as em units are relative to the same element’s font-size value. But what I quickly noticed is that container query units relate to the nearest parent that is also a container.
For example, 5cqw does not scale based on the .card element’s width in this example:
.card { container: card / size; container-name: card; font-size: 5cqw; }Rather, it scales to whatever the nearest parent that’s defined as a container. That’s why I set up a .card__inner wrapper.
CodePen Embed Fallback Case 2: Alternating layoutI needed yet another card component in a different project. This time, I needed the card to transition from a landscape layout to a portrait layout… then back to landscape, and back to portrait again as the screen gets smaller.
I did the dirty work of making this component go to portrait at those two specific viewport ranges (shout out to the new media query range syntax!), but again, the problem is that it is then locked to the media queries set on it, its parent, and anything else that might respond to the viewport’s width. We want something that works in any condition without worrying about wondering where the content is going to break!
Container queries would have made this a breeze, thanks to the @container rule:
.info-card { container-type: inline-size; container-name: info-card; } @container info-card (max-width: 500px) { .info-card__inner { flex-direction: column; } }One query, infinite fluidity:
CodePen Embed FallbackBut hold on! There’s something you might want to watch out for. Specifically, it could be difficult to use a container query like this within a prop-based design system. For example, this .info-card component could contain child components that rely on props to change their appearance.
Why’s that a big deal? The card’s portrait layout might require the alternate styling but you can’t change JavaScript props with CSS. As such, you risk duplicating the required styles. I actually touched on this and how to work around it in another article. If you need to use container queries for a significant amount of your styling, then you may need to base your entire design system around them rather than trying to shoehorn them into an existing design system that’s heavy on media queries.
Case 3: SVG strokesHere’s another super common pattern I’ve recently used where container size queries would have resulted in a more polished product. Say you have an icon locked up with a heading:
<h2> <svg> <!-- SVG stuff --> </svg> Heading </h2>It’s pretty straightforward to scale the icon with the title’s size, even without media queries. The problem, though, is that the SVG’s stroke-width might get too thin to be noticed all that well at a smaller size, and perhaps catch too much attention with a super thick stroke at a larger size.
I’ve had to create and apply classes to each icon instance to determine its size and stroke width. That’s OK if the icon is next to a heading that’s styled with a fixed font size, I guess, but it’s not so great when working with fluid type that constantly changes.
The heading’s font size might be based on the viewport’s width, so the SVG icon needs to adjust accordingly where its stroke works at any size. You could make the stroke width relative to the heading’s font-size by setting it in em units. But if you have a specific set of stroke sizes that you need to stick to, then this wouldn’t work because it otherwise scales linearly — there’s no way to adjust it to a specific stroke-width value at certain points without resorting to media queries on the viewport width.
But here’s what I would have done if I had the luxury of container queries at that time:
.icon { container: icon / size; width: 1em; height: 1em; } .icon svg { width: 100%; height: 100%; fill: none; stroke: #ccc; stroke-width: 0.8; } @container icon (max-width: 70px) { .icon svg { stroke-width: 1.5; } } @container icon (max-width: 35px) { .icon svg { stroke-width: 3; } }Compare the implementations and see how the container query version snaps the SVG’s stroke to the specific widths I want based on the container’s width.
CodePen Embed Fallback Bonus: Other types of container size queriesOK, so I haven’t actually run into this on a real project. But as I was combing through information on container queries, I noticed that there are additional things we can query on a container that are related to the container’s size or physical dimensions.
Most examples I’ve seen query the width, max-width, and min-width, height, block-size, and inline-size as I’ve been doing throughout this article.
@container info-card (max-width: 500px) { .info-card__inner { flex-direction: column; } }But MDN outlines two more things we can query against. One is orientation which makes perfect sense because we use it all the time in media queries. It’s no different with container queries:
@media screen (orientation: landscape) { .info-card__inner { /* Style away! */ } } @container info-card (orientation: landscape) { .info-card__inner { /* Style away! */ } }The other? It’s aspect-ratio, believe it or not:
@container info-card (aspect-ratio: 3/2) { .info-card__inner { /* Style away! */ } }Here’s an editable demo to play around with both examples:
CodePen Embed FallbackI haven’t really found a good use case for either of these yet. If you have any ideas or feel like it could’ve helped you in your projects, let me know in the comments!
A Few Times Container Size Queries Would Have Helped Me Out originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
