Archiwum kategorii: CSS

The truth about CSS selector performance

Post pobrano z: The truth about CSS selector performance

Geez, leave it to Patrick Brosset to talk CSS performance in the most approachable and practical way possible. Not that CSS is always what’s gunking up the speed, or even the lowest hanging fruit when it comes to improving performance.

But if you’re looking for gains on the CSS side of things, Patrick has a nice way of sniffing out your most expensive selectors using Edge DevTools:

  • Crack open DevTools.
  • Head to the Performance Tab.
  • Make sure you have the “Enable advanced rendering instrumentation” option enabled. This tripped me up in the process.
  • Record a page load.
  • Open up the “Bottom-Up” tab in the report.
  • Check out your the size of your recalculated styles.

DevTools with Performance tab open and a summary of events.

From here, click on one of the Recalculated Style events in the Main waterfall view and you’ll get a new “Selector Stats” tab. Look at all that gooey goodness!

Now you see all of the selectors that were processed and they can be sorted by how long they took, how many times they matched, the number of matching attempts, and something called “fast reject count” which I learned is the number of elements that were easy and quick to eliminate from matching.

A lot of insights here if CSS is really the bottleneck that needs investigating. But read Patrick’s full post over on the Microsoft Edge Blog because he goes much deeper into the why’s and how’s, and walks through an entire case study.

To Shared Link — Permalink on CSS-Tricks


The truth about CSS selector performance originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Animating CSS Grid (How To + Examples)

Post pobrano z: 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?

Example 1: Expanding sidebar

First of all, this is what we’re talking about:

CodePen Embed Fallback

A 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 Fallback

This 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 Fallback

This 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 examples

Because 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 Fallback

Same concept, but with more of that Michelle Barker pizzazz. Could make a nice loading spinner?

CodePen Embed Fallback

Wrapping 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 Fallback

Animating 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

Post pobrano z: 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 project

Spinning 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 routing

Notice 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:

Initial files.

You should be able to navigate around by changing URL paths in the browser address bar.

Browser address bar with localhost URL.

Layouts

We’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.

Horizontal navigation with light yellow background.

Nested layouts

What 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:

Red box beneath navigation that says this is an admin page.

Defining our data

OK, 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 data

How 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!

Five to-do items in a table format.

Layout groups

Before 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:

File directory.

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 locations

Let’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.

Our Details page

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:

List page with to-do-items.

Now let’s click the Edit button for one of the to-do items to bring up the editing page in /details.

Details page for a to-do item.

We’re going to add a new title:

Changing the to-do title in an editable text input.

Now, click Save. That should get us back to our /list page, with the new to-do title applied.

The edited to-do item in the full list view.

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 up

We’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:


Getting Started With SvelteKit originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

More Real-World Uses for :has()

Post pobrano z: More Real-World Uses for :has()

The :has() pseudo-class is, hands-down, my favorite new CSS feature. I know it is for many of you as well, at least those of you who took the State of CSS survey. The ability to write selectors upside down gives us more superpowers I’d never thought possible.

I say “more superpowers” because there have already been a ton of really amazing clever ideas published by a bunch of super smart people, like:

This article is not a definitive guide to :has(). It’s also not here to regurgitate what’s already been said. It’s just me (hi 👋) jumping on the bandwagon for a moment to share some of the ways I’m most likely to use :has() in my day-to-day work… that is, once browser support is good enough. (Firefox is the last holdout, but will support it, which is imminent.)

When that does happen, you can bet I’ll start using :has() all over the place. Here are some real-world examples of things I’ve built recently and thought to myself, “Gee, this’ll be so much nicer once :has() is fully supported.”

Avoid having to reach outside your JavaScript component

Have you ever built an interactive component that sometimes needs to affect styles somewhere else on the page? Take the following example, where <nav> is a mega menu, and opening it changes the colors of the <header> content above it.

I feel like I need to do this kind of thing all the time.

This particular example is a React component I made for a site. I had to “reach outside” the React part of the page with document.querySelector(...) and toggle a class on the <body>, <header>, or another component. That’s not the end of the world, but it sure feels a bit yuck. Even in a fully React site (a Next.js site, say), I’d have to choose between managing a menuIsOpen state way higher up the component tree, or do the same DOM element selection — which isn’t very React-y.

With :has(), the problem goes away:

header:has(.megamenu--open) {
  /* style the header differently if it contains 
    an element with the class ".megamenu--open"
  */
}

No more fiddling with other parts of the DOM in my JavaScript components!

Better table striping UX

Adding alternate row “stripes” to your tables can be a nice UX improvement. They help your eyes keep track of which row you’re on as you scan the table.

But in my experience, this doesn’t work great on tables with just two or three rows. If you have, for example, a table with three rows in the <tbody> and you’re “striping” every “even” row, you could end up with just one stripe. That’s not really worth a pattern and might have users wondering what’s so special about that one highlighted row.

Using this technique where Bramus uses :has() to apply styles based on the number of children, we can apply table stripes when there are more than, say, three rows:

CodePen Embed Fallback

What to get fancier? You could also decide to only do this if the table has at least a certain number of columns, too:

table:has(:is(td, th):nth-child(3)) {
  /* only do stuff if there are three or more columns */
}

Remove conditional class logic from templates

I often need to change a page layout depending on what’s on the page. Take the following Grid layout, where the placement of the main content changes grid areas depending on whether there’s a sidebar present.

Layout with left sidebar above a layout with no sidebar.

That’s something that might depend on whether there are sibling pages set in the CMS. I’d normally do this with template logic to conditionally add BEM modifier classes to the layout wrapper to account for both layouts. That CSS might look something like this (responsive rules and other stuff omitted for brevity):

/* m = main content */
/* s = sidebar */
.standard-page--with-sidebar {
  grid-template-areas: 's s s m m m m m m m m m';
}
.standard-page--without-sidebar {
  grid-template-areas: '. m m m m m m m m m . .';
}

CSS-wise, this is totally fine, of course. But it does make the template code a little messy. Depending on your templating language it can get pretty ugly to conditionally add a bunch of classes, especially if you have to do this with lots of child elements too.

Contrast that with a :has()-based approach:

/* m = main content */
/* s = sidebar */
.standard-page:has(.sidebar) {
  grid-template-areas: 's s s m m m m m m m m m';
}
.standard-page:not(:has(.sidebar)) {
  grid-template-areas: '. m m m m m m m m m . .';
}

Honestly, that’s not a whole lot better CSS-wise. But removing the conditional modifier classes from the HTML template is a nice win if you ask me.

It’s easy to think of micro design decisions for :has() — like a card when it has an image in it — but I think it’ll be really useful for these macro layout changes too.

Better specificity management

If you read my previous article, you’ll know I’m a stickler for specificity. If, like me, you don’t want your specificity scores blowing out when adding :has() and :not() throughout your styles, be sure to use :where().

That’s because the specificity of :has() is based on the most specific element in its argument list. So, if you have something like an ID in there (why, I’m not sure!), your selector is going to be tough to override in the cascade.

On the other hand, the specificity of :where() is always zero, never adding to the specificity score.

/* specificity score: 0,1,0.
  Same as a .standard-page--with-sidebar 
  modifier class
*/
.standard-page:where(:has(.sidebar)) {
  /* etc */
}

The future’s bright

These are just a few things I can’t wait to be able to use in production. The CSS-Tricks Almanac has a bunch of examples, too. What are you looking forward to doing with :has()? What sort of some real-world examples have you run into where :has() would have been the perfect solution?


More Real-World Uses for :has() 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

Post pobrano z: 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 important

Manifest 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:

  1. The transition has been ongoing since 2018.
  2. Manifest V3 will officially begin rolling out in January 2023.
  3. By June 2023, extensions that run Manifest V2 will no longer be available on the Chrome Web Store.
  4. 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 2023 June 2023 January 2024
Support 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 V3

There 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:

  1. Service workers replace background pages in Manifest V3.
  2. Network request modification is handled with the new declarativeNetRequest API in Manifest V3.
  3. In Manifest V3, extensions can only execute JavaScript that is included within their package and cannot use remotely-hosted code.
  4. Manifest V3 introduces promise support to many methods, though callbacks are still supported as an alternative.
  5. Host permissions in Manifest V3 are a separate element and must be specified in the "host_permissions" field.
  6. 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 V3

I 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:

  1. Updating your Manifest’s basic structure.
  2. Modify your host permissions.
  3. Update the content security policy.
  4. 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 structure

Updating 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 permissions

The 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 policy

In 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 handling

The 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 check

What 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.

Behind the CSScenes, September 2022

Post pobrano z: Behind the CSScenes, September 2022

Those of you who have been reading CSS-Tricks for a while may remember that we used to publish a little thing we called CSS-Tricks Chronicles. Our friend Chris Coyier would write up a reflection from the past couple of months or so, and it was a great way to get a pulse on what’s happening around CSS-Tricks, the site, and what the team is doing.

We like that and want to keep it going. It’s a new era, though! So what we’re going to do is welcome you back to what we’re now calling Behind the CSScenes. You’re going to meet some new and familiar faces in these updates, starting with Haley Mills, who is kicking off the very first issue.

How’s the transition going?

[Haley Mills:] Before we dive in, let me start by introducing myself! My name is Haley, and I’m the manager of Content Integration here at DigitalOcean. I’ve been at DigitalOcean for 5 years and previously worked on our editorial team, helping authors publish all sorts of topics through our Write For DOnations program. 

Many folks here at DigitalOcean (including myself) are avid readers of CSS-Tricks, and we still have to pinch ourselves for how lucky we are to be entrusted with this community. We recognize that CSS-Tricks is a critical free resource for devs across the world, and my goal is to keep it that way. 

  • Since the acquisition, we have published 95 pieces of new content and look forward to growing that number.
  • In the month of August, we performed maintenance on 6 existing pieces of content.

That said, change is to be expected when passing a torch.

I think we all know that no one can replace Chris’ voice — it’s a big reason why CSS-Tricks is, well, CSS-Tricks. His ability to have you laughing while learning something new is a skill that few can compete with. I know many of you miss his writing because you told us so in a survey (which we’ll get to in a bit), but it also opens up a huge opportunity for us all to take the torch and continue doing what CSS-Tricks does best:

Find creative solutions to problems and share them with the world. Chris brought people together this way on CSS-Tricks — and you can give back, too.

Your blossoming idea could turn out to be what the Flexbox Guide is for me and so many other people, so I humbly encourage you to reach out in our Guest Writing Form and talk to us about your topic ideas. We have two awesome editors, Geoff and Brad, to help you shape and bring your ideas to life to share with the CSS-Tricks community. In addition to paying you for your contribution, we will now also make a matching donation to a tech-focused charity of your choice.

Next up, we have Product Manager Karen Degi with some survey result highlights.

The results are in…

[Karen Degi:] In June, we shared a survey to collect feedback to help shape the future of CSS-Tricks. We received almost 900 responses, including some great written responses that helped us understand what CSS-Tricks means to the larger community. 

Many of you also volunteered to talk to us directly, which has us thinking about the best way to gather those thoughts. If you’re one of those folks, know that we haven’t forgotten about you and still want to hear from you. We just want to make sure we approach this in the most effective way!

The survey confirmed some things we already suspected and brought new things to our attention. The top few things that grabbed our attention are:

  • Engaging, high-quality content is at the heart of CSS-Tricks. We’re working to make sure that we continue investing in in-depth guides on front-end topics, as well as providing short articles about quick tricks and tutorials with embedded demos.
  • You love RSS! As we continue investing in CSS-Tricks and bringing new functionality, we’ll make sure we keep an eye on how our changes affect the RSS feed.
  • You come to CSS-Tricks to learn, to be entertained, and to do your jobs better. You do not come to CSS-Tricks because you’re excited about being sold…well, anything, really. Although we think DigitalOcean is pretty great, and we’ll probably continue to talk about ourselves where it makes sense, we understand that we need to do so in a way that is honest, trustworthy, and connected to your needs as a front-end enthusiast.

Next up is Logan Liffick, Senior Digital Experience Designer, with redesign updates.

A redesign is in the works!

[Logan Liffick:] If you’ve worked on the front end — or really anywhere on the web, you’re bound to know CSS-Tricks. It’s where I, and many others, started the journey. So, when I was asked to spearhead a redesign for the site, it was nothing short of an honor. Without a doubt, undertaking a brand update for something so familiar to so many is a challenge of incredible magnitude

If I were to do justice to this project, I’d need to pay tribute to the original. That mentality became the underlying theme of my work, and any effort to rejuvenate took inspiration from existing patterns and styles from the site.

  • Slideshow of Redesign Preview

Upon first glance, you’ll notice the fresh coat of paint. Past that, you’ll recognize the site reads more “editorial” than before. This was a purposeful decision to accentuate existing type stylings and, more importantly, to pay homage to the essence of CSS-Tricks as an informational resource. 

Preserving the element of “fun” was also top of mind. Sprinkled throughout the site are various snippets from the actual CSS “tricks” shared on this site — for example, there’s going to be a little Easter egg tucked inside a sticky footer using Preethi’s slide-out effect that’s my personal favorite, a fantastic suggestion from Geoff himself. Gradients are now a core color-way in the system, and border-radii have been rounded out. 

We wanted to give ourselves permission and space to explore an open-ended and malleable system far into the future, which lines up nicely with the overall mission and goal of CSS-Tricks: to explore what’s possible with CSS. This is just the beginning, there’s so much more to see, do, and learn with CSS-Tricks living in our (digital) ocean.

Next is Geoff with author highlights!

New authors!

[Geoff:] We’ve added a few new faces to our growing list of guest authors who have contributed to CSS-Tricks:

You may have also seen our editor Bradley Kouchi’s name pop up a couple of times, and you can expect to continue seeing him on a semi-regular basis.

That’s 16 new authors! You can be one, too, by filling out our guest writing form.

On a related note, I’m pleased as punch that we still get regular contributions from a large band of familiar faces from before the DigitalOcean acquisition. Just look at all the fine folks who’ve continued to share their great ideas with us:

Big shake-ups like the one we’re going through today can be scary. Seeing these familiar names in article bylines has helped me a ton as far as continuity and consistency go. CSS-Tricks still seems very CSS-Tricks-y to me, and that’s a big deal.

Until next time…

We hope you’ve enjoyed this little peek behind the CSScenes! We’ll do it again… and again and again. As you can tell, there’s a lot of activity happening around here, which means we’ll have lots to share in the next edition.

Oh, and if you’re one of the many who’ve told us just how much you miss the newsletter, it’s still here! We’re sending it just once a month while we get back in the swing of things, and you may very well need to re-subscribe to get it (we had to do a lot of scrubbing after the keys to the site were handed over).

Thanks for reading!


Behind the CSScenes, September 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Comparing JAWS, NVDA, and VoiceOver

Post pobrano z: Comparing JAWS, NVDA, and VoiceOver

A screen reader is an important accessibility tool for people with no or limited vision. People who are blind or those with low vision can use a screen reader to navigate the computer. Screen readers will read contents on the screen and explain to the user what is on the page. Screen readers allow people to use the computer for daily tasks.

There are many screen reader software available for people through their operating system or through open source projects.

A 2021 research by WebAim found that from 1568 responders, more than 53.7 percent of people surveyed used JAWS on Windows, more than 30.7 percent of people used NVDA on Windows and little over 6.5 percent of people used VoiceOver on macOS.

JAWS and NVDA for Windows and VoiceOver for macOS are the most popular screen readers people use.

First, I should clarify that this article will be written from my point of view. To give background, I have been a front-end developer at a non-profit for people with learning differences for over three years. I, along with my colleagues, seek to make our projects more accessible every day. I am not visually impaired and do not use these tools on a regular basis. For work, I have a Mac machine and test accessibility using VoiceOver.

Here is my planned testing methodology:

  1. Navigate the page by heading, until “Accessibility APIs” section.
  2. In the “Accessibility APIs” section, read the content and the unordered list inside.
  3. TAB to hear focusable items in the unordered list.
  4. Jump to the Search field.
  5. TAB to hear a few items in the navigation section

To find similarities and differences between them, I decided to test a set of steps with each screen reader on a Wikipedia page about screen readers. I will browse the web with Chrome for my tests. Testing all screen readers on the same page and browser will reduce the amount of variables and keep the tests consistent.

JAWS

JAWS is an acronym for Job Access With Speech and is the most widely used screen reader in the world. It is only available on Windows. Depending on the plan and features, JAWS can be purchased anywhere from $90 yearly license all the way to $1605 for perpetual license.

JAWS has predefined keyboard commands to navigate the web. Full list of keyboard commands can be found on their website.

Demonstration

JAWS Demo

In the beginning of the demo, I am clicking on H key on my keyboard to go to the next heading. JAWS is moving down the page, reading me the headings along with their level.

Later in the video, I am clicking on number 2 and number 3 on my keyboard to have JAWS read Heading Levels 2s then later Heading Levels 3s. This is a great feature because we can move down the page and sections by heading level and get a better sense of the page layout.

When I reach the “Accessible APIs” section, I press the DOWN ARROW key until the third item in the unordered list.

Later in the demo, I am clicking on the TAB key for JAWS to read to me the next focusable item on the page, which is inside this list. I click TAB until I reach a focusable element in another section.

Then I press F key to focus on the search field, which JAWS reads to me.

Then I click on TAB and JAWS focuses on the navigation elements that are on the side of the page.

Pros & Cons

Pros:

  • JAWS is more customizable than other screen readers.
  • There are more options to navigate through the page.
  • JAWS is industry standard.
  • Widely used, which means there are lots of user to user support.

Cons:

  • JAWS is more complicated to use than NVDA or VoiceOver.
  • Some commands are not intuitive.
  • There are a lot more commands for the user to learn.
  • More learning curve for users.
  • JAWS is also not available on the Mac, which limits its users.
  • Costs anywhere between $90 – $1605 for the user.
  • JAWS has different key commands for desktop and laptop which may make it harder for users to transfer knowledge and may cause confusion.

NVDA

NVDA, or NonVisual Digital Access, is available on Windows only. Users need to download the software from NVDA’s website, NVAccess. This software is free to download but does not come already installed on Windows machines. NVDA is the second most popular screen reader in the world according to WebAim’s 2021 survey.

Like other screen readers, NVDA has defined keyboard commands to navigate the web. NVDA’s full keyboard commands can be found on their website.

Demonstration

NVDA Demo

In the demo I am clicking on H key on the keyboard to go to the next heading. First, NVDA reads me Heading Level 1, which is “Screen reader”. Then NVDA goes to read Heading Level 2s and 3s.

When I reach “References” I begin to click on TAB on my keyboard for NVDA to focus on next focusable items.

After focusing on a few items on the list, I click ENTER and go to the New York Times page.

Pros & Cons

Pros:

  • Overall, I found NVDA was able to provide me with information on the screen.
  • The out-of-the-box keyboard commands were easy to use and easy to learn.
  • NVDA is open source, which means the community can update and fix.
  • NVDA is free, which makes it an affordable option to Windows users.

Cons:

  • NVDA is not available on the Mac, which limits its users.

VoiceOver

VoiceOver is the screen reader used in Mac. VoiceOver is only available on Mac not available in Windows. VoiceOver is free and is already installed on the computer, which removes barriers because this is part of the computer setup and the user does not have to download or purchase any additional software.

VoiceOver has defined keyboard commands to navigate the web. VoiceOver’s full keyboard commands can be found on their website.

Demonstration

VoiceOver Demo

In the demo, I am on a Wikipedia page and I am clicking on the VoiceOver Command (which is Control+Option) along with Command+H to navigate through the headings. VoiceOver reads the headings in order, starting from Heading Level 1, “Screen Reader”, to Heading Level 2, “Contents”, to Heading Level 3, and so on.

When I reach the “Accessibility APIs” section, I click on VoiceOver Command plus the RIGHT ARROW, to tell VoiceOver that I want it to read this section. Later I am clicking on the VoiceOver Command plus the RIGHT ARROW on my keyboard, to navigate the section.

When I get on to the third item on the unordered list, I press TAB on my keyboard to focus on the next focusable element.

I press TAB a few times, then I press VoiceOver Command plus U, to open the Form Control Menu. In the menu, I press DOWN ARROW until I hear the “Search Wikipedia” option. When I hear it, I click ENTER and the screen reader focuses on the form field. In the form field, I press TAB to navigate to the navigation section.

Pros & Cons

Pros:

  • VoiceOver is easy to use and learn.
  • VoiceOver’s commands are intuitive.
  • Free tool that comes installed in every macOS device.

Cons:

  • VoiceOver is also not available on Windows, which limits its users.
  • VoiceOver is not an app and can only be updated when Apple releases macOS update.

Key Takeaways

A screen reader is an important accessibility tool for people with no or limited vision. Screen readers allow people to use the computer for daily tasks.

There are many screen reader softwares available. In this article I compared JAWS, NVDA, and VoiceOver.

Here is a comparison chart overview of the three screen readers:

JAWS NVDA VoiceOver
Operating System Windows Windows macOS
Price $90 – $1695 Free Free
# of users 30% 50% 6%
Ease of Use Hard Easy Easy

I found that for basic screen reader testing, most screen readers follow a similar keystroke pattern and knowledge from one screen reader can be used for others.

All screen readers have their pros and cons. Ultimately, it’s up to user preference and also the operating system they use to determine which screen reader software is best for them.

Previously: “Small Tweaks That Can Make a Huge Impact on Your Website’s Accessibility” (2018), and “Why, How, and When to Use Semantic HTML and ARIA” (2019), “15 Things to Improve Your Website Accessibility” (2020), “5 Accessibility Quick Wins You Can Implement Today” (2022).


Comparing JAWS, NVDA, and VoiceOver originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

iShadeed’s Container Queries Lab

Post pobrano z: iShadeed’s Container Queries Lab

Ahmad Shadeed got an early jump on container queries and has a growing collection of examples based on everyday patterns.

And, if you missed it, his latest post on container queries does a wonderful job covering how they work since landing in Chrome 105 this month (we’ll see them in Safari 16 soon). Some choice highlights and takeaways:

  • Containers are defined with the container-type property. Previous demos and proposals had been using contain instead.
  • Container queries are very much like the media queries we’ve been writing all along to target the viewport size. So, rather than something like @media (min-width: 600px) {}, we have @container (min-width: 600px) {}. That should make converting many of those media queries to container queries fairly straightfoward, minus the work of figuring out the new breakpoint values.
  • We can name containers to help distinguish them in our code (e.g. container-name: blockquote).

Great job, Ahmad! And thanks for sharing!

To Shared Link — Permalink on CSS-Tricks


iShadeed’s Container Queries Lab originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Interpolating Numeric CSS Variables

Post pobrano z: Interpolating Numeric CSS Variables

We can make variables in CSS pretty easily:

:root {
  --scale: 1;
}

And we can declare them on any element:

.thing {
  transform: scale(var(--scale));
}

Even better for an example like this is applying the variable on a user interaction, say :hover:

:root {
  --scale: 1;
}

.thing {
  height: 100px;
  transform: scale(var(--scale));
  width: 100px;
}

.thing:hover {
  --scale: 3;
}
CodePen Embed Fallback

But if we wanted to use that variable in an animation… nada.

:root {
  --scale: 1;
}

@keyframes scale {
  from { --scale: 0; }
  to { --scale: 3; }
}

/* Nope! */
.thing {
  animation: scale .25s ease-in;
  height: 100px;
  width: 100px;
}

That’s because the variable is recognized as a string and what we need is a number that can be interpolated between two numeric values. That’s where we can call on @property to not only register the variable as a custom property, but define its syntax as a number:

@property --scale {
  syntax: "<number>";
  initial-value: 1;
  inherits: true;
}

Now we get the animation!

CodePen Embed Fallback

You’re going to want to check browser support since @property has only landed in Chrome (starting in version 85) as of this writing. And if you’re hoping to sniff it out with @supports, we’re currently out of luck because it doesn’t accept at-rules as values… yet. That will change once at-rule()becomes a real thing.

CodePen Embed Fallback

Interpolating Numeric CSS Variables originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Removing jQuery from GOV.UK

Post pobrano z: Removing jQuery from GOV.UK

The GOV.UK team recently published “How and why we removed jQuery from GOV.UK“. This was an insightful look at how an organization can assess its tooling and whether something is still the best tool for the job. This is not a nudge to strip libraries out of your current project right now! Many of us may still be supporting legacy projects and browser requirements that prevent this from being a viable option.

Some of the criticism appears to be that the library size argument is negligible on modern network speeds and caching.

GOV.UK posted an update to address this criticism with metrics – “The impact of removing jQuery on our web performance“.

This article also makes the case for improving maintenance. Instead of upgrading disparate outdated versions of code and having to address security updates in a piecemeal approach, removing the dependency reduces this footprint. This is the dream of having the luxury for addressing technical debt.

Previously, GitHub also documented how they incrementally decoupled jQuery from their front-end code. Improving maintenance and developer experience played a role into their decision.

What caught my eye in particular was the link to the documentation on how to remove jQuery. Understanding how to decouple and perform migration steps are maintenance tasks that will continue to come up for websites and it’s reassuring to have a guide from someone that had to do the same.

Further musing on this subject turned up the old chestnuts “You Might Not Need jQuery” (2014), “(Now More Than Ever) You Might Not Need jQuery” (2017), “Is jQuery still relevant? (1)” (2016), and “Is jQuery still relevant? (2)” (2017).

To Shared Link — Permalink on CSS-Tricks


Removing jQuery from GOV.UK originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.