Tag: Know

Some Cross-Browser DevTools Features You Might Not Know

I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in one browser’s DevTools will be the same or supported in another browser’s DevTools.

But there are quite a few DevTools features that are interoperable, even some lesser-known ones that I’m about to share with you.

For the sake of brevity, I use “Chromium” to refer to all Chromium-based browsers, like Chrome, Edge, and Opera, in the article. Many of the DevTools in them offer the exact same features and capabilities as one another, so this is just my shorthand for referring to all of them at once.

Search nodes in the DOM tree

Sometimes the DOM tree is full of nodes nested in nodes that are nested in other nodes, and so on. That makes it pretty tough to find the exact one you’re looking for, but you can quickly search the DOM tree using Cmd + F (macOS) or Ctrl + F (Windows).

Additionally, you can also search using a valid CSS selector, like .red, or using an XPath, like //div/h1.

DevTools screenshots of all three browsers.
Searching text in Chrome DevTools (left), selectors in Firefox DevTools (center), and XPath in Safari DevTools (right)

In Chromium browsers, the focus automatically jumps to the node that matches the search criteria as you type, which could be annoying if you are working with longer search queries or a large DOM tree. Fortunately, you can disable this behavior by heading to Settings (F1) → PreferencesGlobalSearch as you typeDisable.

After you have located the node in the DOM tree, you can scroll the page to bring the node within the viewport by right-clicking on the nod, and selecting “Scroll into view”.

Showing a highlighted node on a webpage with a contextual menu open to scroll into view

Access nodes from the console

DevTools provides many different ways to access a DOM node directly from the console.

For example, you can use $ 0 to access the currently selected node in the DOM tree. Chromium browsers take this one step further by allowing you to access nodes selected in the reverse chronological order of historic selection using, $ 1, $ 2, $ 3, etc.

Currently selected node accessed from the Console in Edge DevTools

Another thing that Chromium browsers allow you to do is copy the node path as a JavaScript expression in the form of document.querySelector by right-clicking on the node, and selecting CopyCopy JS path, which can then be used to access the node in the console.

Here’s another way to access a DOM node directly from the console: as a temporary variable. This option is available by right-clicking on the node and selecting an option. That option is labeled differently in each browser’s DevTools:

  • Chromium: Right click → “Store as global variable”
  • Firefox: Right click → “Use in Console”
  • Safari: Right click → “Log Element”
Screenshot of DevTools contextual menus in all three browsers.
Access a node as a temporary variable in the console, as shown in Chrome (left), Firefox (center), and Safari (right)

Visualize elements with badges

DevTools can help visualize elements that match certain properties by displaying a badge next to the node. Badges are clickable, and different browsers offer a variety of different badges.

In Safari, there is a badge button in the Elements panel toolbar which can be used to toggle the visibility of specific badges. For example, if a node has a display: grid or display: inline-grid CSS declaration applied to it, a grid badge is displayed next to it. Clicking on the badge will highlight grid areas, track sizes, line numbers, and more, on the page.

A grid overlay visualized on top of a three-by-three grid.
Grid overlay with badges in Safari DevTools

The badges that are currently supported in Firefox’s DevTools are listed in the Firefox source docs. For example, a scroll badge indicates a scrollable element. Clicking on the badge highlights the element causing the overflow with an overflow badge next to it.

Overflow badge in Firefox DevTools located in the HTML panel

In Chromium browsers, you can right-click on any node and select “Badge settings…” to open a container that lists all of the available badges. For example, elements with scroll-snap-type will have a scroll-snap badge next to it, which on click, will toggle the scroll-snap overlay on that element.

Taking screenshots

We’ve been able to take screenshots from some DevTools for a while now, but it’s now available in all of them and includes new ways to take full-page shots.

The process starts by right-clicking on the DOM node you want to capture. Then select the option to capture the node, which is labeled differently depending on which DevTools you’re using.

Screenshot of DevTools in all three browsers.
Chrome (left), Safari (middle), and Firefox (right)

Repeat the same steps on the html node to take a full-page screenshot. When you do, though, it’s worth noting that Safari retains the transparency of the element’s background color — Chromium and Firefox will capture it as a white background.

Two screenshots of the same element, one with a background and one without.
Comparing screenshots in Safari (left) and Chromium (right)

There’s another option! You can take a “responsive” screenshot of the page, which allows you to capture the page at a specific viewport width. As you might expect, each browser has different ways to get there.

  • Chromium: Cmd + Shift + M (macOS) or Ctrl + Shift + M (Windows). Or click the “Devices” icon next to the “Inspect” icon.
  • Firefox: Tools → Browser Tools → “Responsive Design Mode”
  • Safari: Develop → “Enter Responsive Design Mode”
Enter responsive mode options in DevTools for all three browsers.
Launching responsive design mode in Safari (left), Firefox (right), and Chromium (bottom)

Chrome tip: Inspect the top layer

Chrome lets you visualize and inspect top-layer elements, like a dialog, alert, or modal. When an element is added to the #top-layer, it gets a top-layer badge next to it, which on click, jumps you to the top-layer container located just after the </html> tag.

The order of the elements in the top-layer container follows the stacking order, which means the last one is on the top. Click the reveal badge to jump back to the node.

Firefox tip: Jump to ID

Firefox links the element referencing the ID attribute to its target element in the same DOM and highlights it with an underline. Use CMD + Click (macOS) or CTRL + Click (Windows) )to jump to the target element with the identifier.

Wrapping up

Quite a few things, right? It’s awesome that there are some incredibly useful DevTools features that are supported in Chromium, Firefox, and Safari alike. Are there any other lesser-known features supported by all three that you like?

There are a few resources I keep close by to stay on top of what’s new. I thought I’d share them with here:

Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, , , , ,

Everything You Need to Know About the Gap After the List Marker

I was reading “Creative List Styling” on Google’s web.dev blog and noticed something odd in one of the code examples in the ::marker section of the article. The built-in list markers are bullets, ordinal numbers, and letters. The ::marker pseudo-element allows us to style these markers or replace them with a custom character or image.

::marker {   content: url('/marker.svg') ' '; }

The example that caught my attention uses an SVG icon as a custom marker for the list items. But there’s also a single space character (" ") in the CSS value next to the url() function. The purpose of this space seems to be to insert a gap after the custom marker.

When I saw this code, I immediately wondered if there was a better way to create the gap. Appending a space to content feels more like a workaround than the optimal solution. CSS provides margin and padding and other standard ways to space out elements on the page. Could none of these properties be used in this situation?

First, I tried to substitute the space character with a proper margin:

::marker {   content: url('/marker.svg');   margin-right: 1ch; }

This didn’t work. As it turns out, ::marker only supports a small set of mostly text-related CSS properties. For example, you can change the font-size and color of the marker, and define a custom marker by setting content to a string or URL, as shown above. But the margin and padding properties are not supported, so setting them has no effect. What a disappointment.

Could it really be that a space character is the only way to insert a gap after a custom marker? I needed to find out. As I researched this topic, I made a few interesting discoveries that I’d like to share in this article.

Adding padding and margins

First, let’s confirm what margin and padding do on the <ul> and <li> elements. I’ve created a test page for this purpose. Drag the relevant sliders and observe the effect on the spacing on each side of the list marker. Tip: Use the Reset button liberally to reset all controls to their initial values.

Note: Browsers apply a default padding-inline-left of 40px to <ol> and <ul> elements. The logical padding-inline-left property is equivalent to the physical padding-left property in writing systems with a left-to-right inline direction. In this article, I’m going to use physical properties for the sake of simplicity.

As you can see, padding-left on <li> increases the gap after the list marker. The other three properties control the spacing to the left of the marker, in other words, the indentation of the list item.

Notice that even when the list item’s padding-left is 0px, there is still a minimum gap after the marker. This gap cannot be decreased with margin or padding. The exact length of the minimum gap depends on the browser.

First three properties: UL margin-left, UL padding-left, LI margin-left. Fourth property: LI padding-left.
The first three properties push the entire list item (including the marker) to the right. The fourth property pushes only the list item’s content to the right.

To sum up, the list item’s content is positioned at a browser-specific minimum distance from the marker, and this gap can be further increased by adding a padding-left to <li>.

Next, let’s see what happens when we position the marker inside the list item.

Moving the marker inside the list item

The list-style-position property accepts two keywords: outside, which is the default, and inside, which moves the marker inside the list item. The latter is useful for creating designs with full-width list items.

A grocery list. Each item has a thin bottom border that extends from the left to the right edge of the list.
The list marker is positioned inside the list item, so that the list item’s bottom border can extend to the left edge of the list box

If the marker is now inside the list item, does this mean that padding-left on <li> no longer increases the gap after the marker? Let’s find out. On my test page, turn on list-style-position: inside via the checkbox. How are the four padding and margin properties affected by this change?

As you can see, padding-left on <li> now increases the spacing to the left of the marker. This means that we’ve lost the ability to increase the gap after the marker. In this situation, it would be useful to be able to add margin-right to the ::marker itself, but that doesn’t work, as we’ve established above.

The four properties: UL margin-left, UL padding-left, LI margin-left, LI padding-left.
All four properties push the entire list item to the right. The minimum gap cannot be increased by standard means.

Additionally, there’s a bug in Chromium that causes the gap after the marker to triple after switching to inside positioning. By default, the length of the gap is about one-third of the text size. So at a default font-size of 16px, the gap is about 5.5px. After switching to inside, the gap grows to the full 16px in Chrome. This bug affects the disc, circle, and square markers, but not ordinal number markers.

The following image shows the default rendering of outside and inside-positioned list markers across three major browsers on macOS. For your convenience, I’ve horizontally aligned all list items on their markers to make it easier to compare the differences in gap sizes.

Six list items with varying gaps between the marker and text.
Only Firefox maintains the same gap size between the two marker positioning modes. This can be considered a browser interoperability (interop) issue.

To sum up, switching to list-style-position: inside introduces two problems. We can no longer increase the gap via padding-left on <li>, and the gap size is inconsistent between browsers.

Finally, let’s see what happens when we replace the default list marker with a custom marker.

Switching to a custom marker

There are two ways to define a custom marker:

  • list-style-type and list-style-image properties
  • content property on the ::marker pseudo-element

The content property is more powerful. For example, it allows us to use the counter() function to access the list item’s ordinal number (the implicit list-item counter) and decorate it with custom strings.

Unfortunately, Safari doesn’t support the content property on ::marker yet (WebKit bug). For this reason, I’m going to use the list-style-type property to define the custom marker. You can still use the ::marker selector to style the custom marker declared via list-style-type. That aspect of ::marker is supported in Safari.

Any Unicode character can potentially serve as a custom list marker, but only a small set of characters actually have “Bullet” in their official name, so I thought I’d compile them here for reference.

Character Name Code point CSS keyword
Bullet U+2022 disc
Triangular Bullet U+2023
Hyphen Bullet U+2043
Black Leftwards Bullet U+204C
Black Rightwards Bullet U+204D
Inverse Bullet U+25D8
White Bullet U+25E6 circle
Reversed Rotated Floral Heart Bullet U+2619
Rotated Heavy Black Heart Bullet U+2765
Rotated Floral Heart Bullet U+2767
Circled White Bullet U+29BE
⦿ Circled Bullet U+29BF

Note: The CSS square keyword does not have a corresponding “Bullet” character in Unicode. The character that comes closest is the Black Small Square (▪️) emoji (U+25AA).

Now let’s see what happens when we replace the default list marker with list-style-type: "•" (U+2022 Bullet). This is the same character as the default bullet, so there shouldn’t be any major rendering differences. On my test page, turn on the list-style-type option and observe any changes to the marker.

As you can see, there are two significant changes:

  1. There is no longer a minimum gap after the marker.
  2. The bullet has become smaller, as if it were rendered at a smaller font-size.

According to CSS Counter Styles Level 3, the default list marker (disc) should be “similar to • U+2022 BULLET”. It seems that browsers increase the size of the default bullet to make it more legible. Firefox even uses a special font, -moz-bullet-font, for the marker.

:marker selected in the inspector. Fonts used: -moz-bullet-font.
The “Fonts” pane in Firefox’s DOM inspector reveals the special font.

Can the small size problem be fixed with CSS? On my test page, turn on marker styling and observe what happens when you change the font-size, line-height, and font-family of the marker.

As you can see, increasing the font-size causes the custom marker to become vertically misaligned, and this cannot be corrected by decreasing the line-height. The vertical-align property, which could easily fix this problem, is not supported on ::marker.

But did you notice that changing the font-family can cause the marker to become bigger? Try setting it to Tahoma. This could potentially be a good-enough workaround for the small-size problem, although I haven’t tested which font works best across the major browsers and operating systems.

You may also have noticed that the Chromium bug doesn’t occur anymore when you position the marker inside the list item. This means that a custom marker can serve as a workaround for this bug. And this leads me to the main problem, and the reason why I started researching this topic. If you define a custom marker and position it inside the list item, there is no gap after the marker and no way to insert a gap by standard means.

  1. There is no minimum gap after custom markers.
  2. ::marker doesn’t support padding or margin.
  3. padding-left on <li> doesn’t increase the gap, since the marker is positioned inside.


Here’s a summary of all the key facts that I’ve mentioned in the article:

  1. Browsers apply a default padding-inline-start of 40px to <ul> and <ol> elements.
  2. There is a minimum gap after built-in list markers (disc, decimal, etc.). There is no minimum gap after custom markers (string or URL).
  3. The length of the gap can be increased by adding a padding-left to <ul>, but only if the marker is positioned outside the list item (the default mode).
  4. Custom string markers have a smaller default size than built-in markers. Changing the font-family on ::marker can increase their size.


Looking back at the code example from the beginning of the article, I think I understand now why there’s a space character in the content value. There is just no better way to insert a gap after the SVG marker. It’s a workaround that is needed because no amount of margin and padding can create a gap after a custom marker that is positioned inside the list item. A margin-right on ::marker could easily do it, but that is not supported.

Until ::marker adds support for more properties, web developers will often have no choice but to hide the marker and emulate it with a ::before pseudo-element. I had to do that myself recently because I couldn’t change the marker’s background-color. Hopefully, we won’t have to wait too long for a more powerful ::marker pseudo-element.

Everything You Need to Know About the Gap After the List Marker originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, , , , , ,

Newer Things to Know About Good Ol’ HTML Lists

HTML lists are boring. They don’t do much, so we don’t really think about them despite how widely used they are. And we’re still able to do the same things we’ve always done to customize them, like removing markers, reversing order, and making custom counters.

There are, however, a few “newer” things — including dangers — to know when using lists. The dangers are mostly minor, but way more common than you might think. We’ll get to those, plus some new stuff we can do with lists, and even new ways to approach old solutions.

To clarify, these are the HTML elements we’re talking about:

  • Ordered lists <ol>
  • Unordered lists <ul>
  • Description lists <dl>
  • Interactive lists <menu>

Ordered lists, unordered lists, and interactive lists contain list items (<li>) which are displayed according to what kind of list we’re dealing with. An ordered list (<ol>) displays numbers next to list items. Unordered lists (<ul>) and menu elements (<menu>) displays bullet points next to list items. We call these “list markers” and they can even be styled using the ::marker pseudo-element. Description lists use description terms (<dt>) and description details (<dd>) instead of <li> and don’t have list markers. They‘re supposed to be used to display metadata and glossaries, but I can’t say I’ve ever seen them in the wild.

Let’s start off with the easy stuff — how to correctly (at least in my opinion) reset list styles. After that, we’ll take a look at a couple of accessibility issues before shining a light on the elusive <menu> element, which you may be surprised to learn… is actually a type of list, too!

Resetting list styles

Browsers automatically apply their own User Agent styles to help with the visual structure of lists right out of the box. That can be great! But if we want to start with a blank slate free of styling opinions, then we have to reset those styles first.

For example, we can remove the markers next to list items pretty easily. Nothing new here:

/* Zap all list markers! */ ol, ul, menu {   list-style: none; }

But modern CSS has new ways to help us target specific list instances. Let’s say we want to clear markers from all lists, except if those lists appear in long-form content, like an article. If we combine the powers of newer CSS pseudo-class functions :where() and :not(), we can isolate those instances and allow the markers in those cases:

/* Where there are lists that are not articles where there are lists... */ :where(ol, ul, menu):not(article :where(ol, ul, menu)) {   list-style: none; }

Why use :where() instead of :is()? The specificity of :where() is always zero, whereas :is() takes the specificity of the most specific element in its list of selectors. So, using :where() is a less forceful way of overriding things and can be easily overridden itself.

UA styles also apply padding to space a list item’s content from its marker. Again, that’s a pretty nice affordance right out of the box in some cases, but if we’re already removing the list markers like we did above, then we may as well wipe out that padding too. This is another case for :where():

:where(ol, ul, menu) {   padding-left: 0; /* or padding-inline-start */ }

OK, that’s going to prevent marker-less list items from appearing to float in space. But we sort of tossed out the baby with the bathwater and removed the padding in all instances, including the ones we previously isolated in an <article>. So, now those lists with markers sorta hang off the edge of the content box.

Notice that UA styles apply an extra 40px to the <menu> element.

So what we want to do is prevent the list markers from “hanging” outside the container. We can fix that with the list-style-position property:

Or not… maybe it comes down to stylistic preference?

Newer accessibility concerns with lists

Unfortunately, there are a couple of accessibility concerns when it comes to lists — even in these more modern times. One concern is a result of applying list-style: none; as we did when resetting UA styles.

In a nutshell, Safari does not read ordered and unordered lists styled with list-style: none as actual lists, like when navigating content with a screen reader. In other words, removing the markers also removes the list’s semantic meaning. The fix for this fix it to apply an ARIA list role on the list and a listitem role to the list items so screen readers will pick them up:

<ol style="list-style: none;" role="list">   <li role="listItem">...</li>   <li role="listItem">...</li>   <li role="listItem">...</li> </ol>  <ul style="list-style: none;" role="list">   <li role="listItem">...</li>   <li role="listItem">...</li>   <li role="listItem">...</li> </ul>

Oddly, Safari considers this to be a feature rather than a bug. Basically, users would report that screen readers were announcing too many lists (because developers tend to overuse them), so now, only those with role="list" are announced by screen readers, which actually isn’t that odd after all. Scott O’Hara has a detailed rundown of how it all went down.

A second accessibility concern isn’t one of our own making (hooray!). So, you know how you’re supposed to add an aria-label to <section> elements without headings? Well, it sometimes makes sense to do the same with a list that doesn’t contain a heading element that helps describe the list.

<!-- This list is somewhat described by the heading --> <section>   <h2>Grocery list</h2>   <ol role="list">      <!-- ... -->   </ol> </section>  <!-- This list is described by the aria-label --> <ol role="list" aria-label="Grocery list">   <!-- ... --> </ol>

You absolutely don’t have to use either method. Using a heading or an ARIA label is just added context, not a requirement — be sure to test your websites with screen readers and do what offers the best user experience for the situation.

In somewhat related news, Eric Bailey wrote up an excellent piece on why and how he considers aria-label to be a code smell.

Wait, <menu> is a list, too?

OK, so, you’re likely wondering about all of the <menu> elements that I’ve been slipping into the code examples. It’s actually super simple; menus are unordered lists except that they’re meant for interactive items. They’re even exposed to the accessibility tree as unordered lists.

In the early days of the semantic web, I mistakenly believed that menus were like <nav>s before believing that they were for context menus (or “toolbars” as the spec says) because that’s what early versions of the HTML spec said. (MDN has an interesting write-up on all of the deprecated stuff related to <menu> if you’re at all interested.)

Today, however, this is the semantic way to use menus:

<menu aria-label="Share article">   <li><button>Email</button></li>   <li><button>Twitter</button></li>   <li><button>Facebook</button></li> </menu>

Personally, I think there are some good use-cases for <menu>. That last example shows a list of social sharing buttons wrapped up in a labeled <menu> element, the notable aspect being that the “Share article” label contributes a significant amount of context that helps describe what the buttons do.

Are menus absolutely necessary? No. Are they HTML landmarks? Definitely not. But they’re there if you enjoy fewer <div>s and you feel like the component could use an aria-label for additional context.

Anything else?

Yes, there’s also the aforementioned <dl> (description list) element, however, MDN doesn’t seem to consider them lists in the same way — it’s a list of groups containing terms — and I can’t say that I’ve really seen them in use. According to MDN, they’re supposed to be used for metadata, glossaries, and other types of key-value pairs. I would just avoid them on the grounds that all screen readers announce them differently.

But let’s not end things on a negative note. Here’s a list of super cool things you can do with lists:

Newer Things to Know About Good Ol’ HTML Lists originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, , , , , , ,

What CSS Do You Absolutely Have to Know in 2022?

Sacha Greif openly wondered whether CSS has gotten to be, you know, too big. With all the goodies that’ve shipped in browsers the past couple of years — container queries! relative color syntax! cascade layers! logical properties! ranges in media queries! individual transforms! :has() selector! — and all of what’s on the possible horizon — CSS Toggles! nesting! color mixing! scroll-linked animations! scoped styles! — there’s definitely a different learning curve for CSS these days for new and seasoned front-enders alike.

There may have been a time when it was possible to know most CSS properties and how they work. Those days are long-gone, at least for an old hand like me. But that sort of begs the question: what CSS do you absolutely have to know?

Vincas Stonys recently took a stab at a list. Chris put one together based on features released since CSS3. You probably have an idea of what you would include in a list. If I had to put a Top 5 together and limit myself to only properties and selectors, it might look something like this…


I can’t say enough about the writing-mode property. What makes it important — especially from a learning perspective — is that it sets you up for inclusive principles that account for crafting layouts, regardless of the user’s language. A good understanding of writing-mode is going to lead to an understanding of logical properties and values, and those, in turn, set the stage for understanding document flow and thinking in terms of block, inline, start, and end rather than physical directions.


I have a hard time believing anyone can write good CSS without having a solid grasp on the display property. It’s both a property and a framework for creating layouts. There’s no Flexbox or CSS Grid without it, making it sort of like a gatekeeper to understanding those important features.

Plus, the display property perfectly complements writing-mode. It’s exactly what you’ll need once writing-mode has exposed you to document flow and logical directions. You’re going to need a property to either change an element’s normal flow (like changing a block element to an inline one) or start laying things out (like creating a flexible layout context) and that is where display comes into play.

margin / padding / border

Ugh, I’m totally cheating here but think learning margin, padding, and border together is sort of unavoidable. They’re all parts of The Box Model, all help with spacing and styling, and all require getting acquainted with CSS length units. Knowing what these properties are desgined to do and how they contribute to the computed size of an element certainly gives you a lot more styling control, and dispels any confusion about why an element is the size that it is — a common CSS headache!

::before and ::after

Another one where I’m cheating a bit. Yes, ::before and ::after are two individual pseudo-elements, but again, I can’t imagine learning about one without the other. It’s a two-fer!

I remember how mind-blowing it was for me to learn that these existed and can be used to create everything from cool UI effects to complete single-div illustrations. It opens up new possibilities and provides a first peek at how powerful CSS really is.


Oof, I’m already at my fifth and final item in the list and feel like there’s still so much CSS goodness that belongs here. But if I have to choose one last thing, it would be media queries. Why? Because it’s a prime ingredient for creating fluid, flexible layouts and different viewing contexts. Container queries might wind up knocking this off my list as it matures, but for now, @media is a great primer for responsive design.

Beyond that, @media is a nice first step into the conditional qualities of CSS. Whether we’re writing a query based on the type of device thats being used (e.g., screen or print) or a when the browser’s viewport meets a certain criteria (e.g., width >= 768px), the @media syntax is incredibly useful for creating layouts that are optimized for different conditions.

Oh, and we haven’t even touched on how @media relates to accessibility, thanks to its ability to apply styles based on a user’s preferences (e.g., prefers-reduced-motion). So, in addition to crafting conditional layouts, media queries are a nice next step toward understanding inclusive design.

Honerable mentions

Distilling CSS into a list of five must-know properties and selectors is tough, especially now that CSS more powerful today than it was, say, even five years ago. There are a number of other items I really wanted to include, like (in no particular order):

  • calc()
  • has()
  • color
  • font
  • overflow
  • position (especially this)
  • z-index

But I stand by my choices. Learning CSS is more important than memorizing a list of properties. It’s a journey and I think the five I chose carve a nice little learning path that sets the stage for writing good style rules and next steps for diving deeper into CSS.

Alright, tell me yours!

Disagree with my list? You should! I’ll bet you have some smart opinions and I want to see what how you would have rounded out a Top 5 list.

What CSS Do You Absolutely Have to Know in 2022? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, ,

Top Things You Didn’t Know You Could Do With Netlify CLI

(This is a sponsored post.)

First things first, if you didn’t know Netlify had a CLI, they do.  One of my favorite things about it running the command netlify dev on nearly any static-site generator project is seeing it detect what it should be doing and spinning the site up in a dev server for you. But not just any dev server, a dev server that replicates the Netlify environment, meaning things like running your serverless functions and making your environment variables available.

Here are five more things you can do with it that you might not realize.

1) Create a new site from a template

That’s right, spin up a new site by typing a single command and walking through the steps. Try it:

netlify sites:create-template

There is a shorthand to the CLI as well! Try the above as ntl sites:create-template

As Charlie Gerard writes in a blog post about this:

At the moment, our templates include a Gatsby and Hugo starter with the Netlify CMS, as well as a Next.js starter. 

2) Manage your environment variables 

The netlify env command, now in Beta, allows you to control environment variables. You can list them out with netlify env:list, get and set (and unset) them. My favorite: move a whole set of them from one site to another like netlify env:migrate --to <to-site-id>.

3) Test serverless functions

By virtue of spinning up your site locally with the Netlify CLI, your serverless functions will run. You can test that they are working and inspect the network traffic and such that way. But the CLI can help you as well, the netlify functions command is capable of testing functions at the command line level. For example, netlify functions:invoke can trigger a function with simulated data.

4) Live stream your Dev environment

Here’s Melanie Crissey on the Netlify Blog about this:

While Netlify’s collaborative Deploy Previews are our go-to for asynchronous feedback, sometimes you need to drop everything and pair on an issue together. That’s when Netlify Live really shines.

For example, just last week, our team was working quickly to debug some funky edge case issues with authentication for the Your Year on Netlify project. Zach Leatherman, who was working on the fix, spun up a local version of the app with Netlify Live. Within minutes, he was able to see the logs, identify the issue, and make a few changes. Meanwhile, I was able to test out the fix before it was ever deployed—without pulling down a copy of his latest version from a repo. Netlify CLI to the rescue and problem solved!

Remember how I mentioned you spin up a dev environment locally with netlify dev? The trick here is to do netlify dev --live. So rather than a localhost URL that only you would be able to see, you’ll get a special netlify.live URL that the world can see.

5) Run netlify switch to switch between different Netlify accounts, like from your personal side project to a work project

You literally auth with the CLI (netlify login, imagine that), so that you can act on behalf of your own Netlify account. Deploy sites and whatnot. But it’s perfectly reasonable that you have multiple Netlify accounts (like work and personal). Running netlify switch makes it trivial to move between accounts.


This video is 50 seconds long and shows how you can go from having some static files locally to a deployed with the CLI:

Top Things You Didn’t Know You Could Do With Netlify CLI originally published on CSS-Tricks. You should get the newsletter.


, , , , ,

WordPress Caching: All You Need To Know

Here’s Ashley Rich at Delicious Brains writing about all the layers of caching that are relevant to a WordPress site. I think we all know that caching is complicated, but jeez, it’s a journey to understand all the caches at work here. The point of cache being speed and reducing burden on the worst bottlenecks and slowest/busiest parts of a web stack.

Here’s my own understanding:

  • Files can be cached by the browser. This is the fastest possible cache as no network request happens at all. Assets like images, CSS, and JavaScript are often cached this way because they don’t change terribly frequently, but you have to make sure you’re telling browsers that it’s OK to do this and have a mechanism in place to break that cache if you need to (e.g. by changing file names). You very rarely cache the HTML this way, as it changes the most and file-name-cache-busting of HTML seems more tricky than it’s worth.
  • Files can be cached at the CDN level. This is great because even though network traffic is happening, CDN servers are very fast and likely geographically closer to users than your origin server. If users get files from here, they never even trouble your origin server. You’ll need a way to break this cache as well, which again is probably through changing file names. You might cache HTML at this level even without changing file names if you have a mechanism to clear that cache globally when content changes.
  • The origin server might cache built HTML pages. On a WordPress site, the pages are built with PHP which probably triggers MySQL queries. If the server can save the result of the things that have already executed, that means it can serve a “static” file as a response, which it can do much faster than having to run the PHP and MySQL. That’ll work for logged out users, who all get the same response, but not for logged in users who have dynamic content on the page (like the WordPress admin bar).
  • The database has its own special caching. After a MySQL query is executed, the results can be saved in an Object Cache, meaning the same request can come from that cache instead of having to run the query again. You get that automatically to some degree, but ideally it gets wired up to a more persistent store, which you do not get automatically

Phew. It gets a little easier with Jamstack since your pages are prebuilt and CDN-hosted already, and in the case of Netlify, you don’t even have to worry about cache busting.

But even as complex as this is, I don’t worry about it all that much. This WordPress site uses Flywheel for hosting which deals with the database and server-level caching, I have Cloudflare in front of it with special WordPress optimization for the CDN caching, and roll-my-own file-name cache busting (I wish this part was easier). I’d certainly trust SpinupWP to get it right too, given Ashley’s great writeup I’m linking to here.

Direct Link to ArticlePermalink

The post WordPress Caching: All You Need To Know appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Did You Know About the :has CSS Selector?

File this under stuff you don’t need to know just yet, but I think the :has CSS selector is going to have a big impact on how we write CSS in the future. In fact, if it ever ships in browsers, I think it breaks my mental model for how CSS fundamentally works because it would be the first example of a parent selector in CSS.

Before I explain all that, let’s look at an example:

div:has(p) {   background: red; }

Although it’s not supported in any browser today, this line of CSS would change the background of a div only if it has a paragraph within it. So, if there’s a div with no paragraphs in it, then these styles would not apply.

That’s pretty handy and yet exceptionally weird, right? Here’s another example:

div:has(+ div) {    color: blue;  }

This CSS would only apply to any div that directly has another div following it.

The way I think about :has is this: it’s a parent selector pseudo-class. That is CSS-speak for “it lets you change the parent element if it has a child or another element that follows it.” This is so utterly strange to me because it breaks with my mental model of how CSS works. This is how I’m used to thinking about CSS:

/* Not valid CSS, just an illustration */ .parent {   .child {     color: red;   } }

You can only style down, from parent to child, but never back up the tree. :has completely changes this because up until now there have been no parent selectors in CSS and there are some good reasons why. Because of the way in which browsers parse HTML and CSS, selecting the parent if certain conditions are met could lead to all sorts of performance concerns.

Putting those concerns aside though, if I just sit down and think about all the ways I might use :has today then I sort of get a headache. It would open up this pandora’s box of opportunities that have never been possible with CSS alone.

Okay, one last example: let’s say we want to only apply styles to links that have images in them:

a:has(> img) {   border: 20px solid white; }

This would be helpful from time to time. I can also see :has being used for conditionally adding margin and padding to elements depending on their content. That would be neat.

Although :has isn’t supported in browsers right now (probably for those performance reasons), it is part of the CSS Selectors Level 4 specification which is the same spec that has the extremely useful :not pseudo-class. Unlike :has, :not does have pretty decent browser support and I used it for the first time the other day:

ul li:not(:first-of-type) {   color: red; }

That’s great I also love how gosh darn readable it is; you don’t ever have to have seen this line of code to understand what it does.

Another way you can use :not is for margins:

ul li:not(:last-of-type) {   margin-bottom: 20px; }

So every element that is not the last item gets a margin. This is useful if you have a bunch of elements in a card, like this:

CSS Selectors Level 4 is also the same spec that has the :is selector that can be used like this today in a lot of browsers:

:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {   color: #BADA55; }  /* ... which would be the equivalent of: */ section h1, section h2, section h3, section h4, section h5, section h6,  article h1, article h2, article h3, article h4, article h5, article h6,  aside h1, aside h2, aside h3, aside h4, aside h5, aside h6,  nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {   color: #BADA55; }

So that’s it! :has might not be useful today but its cousins :is and :not can be fabulously helpful already and that’s only a tiny glimpse — just three CSS pseudo-classes — that are available in this new spec.

The post Did You Know About the :has CSS Selector? appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

“I Don’t Know”

I’ve learned to be more comfortable not knowing. “I don’t know”, comes easier now. “I don’t know anything about that.” It’s okay. It feels good to say.

Whether it’s service workers, Houdini, shadow DOM, web components, HTTP2, CSS grid, “micro-front ends”, AVIF… there are many paths before us. This list doesn’t even broach JavaScript frameworks and libraries. Much of this tech isn’t even novel in 2020—but together act as a clapperboard cueing in me a familiar fear of missing out or imposter syndrome.

How does someone stay current, let alone learn something new? I am reminded of a comment made by Melanie Sumner recently:

Anyone else feel like paying attention to any specific area of development causes the other skills to rust?

To achieve deeper understanding in a topic, one must seclude themselves to a focused path, etching only a tiny arc on the complete circle that is the web. Mastery of a subject comes with it both the elation of achievement and an awareness of the untraveled, much like Matt Might’s The Illustrated Guide to a Ph.D. Piercing or expanding the boundaries of our own spheres of knowledge is exhilarating, yes. But as Melanie observes, it’s a bit like reaching a remote mountain peak only to see more summits stretching out to the horizon. It’s a solitary place, not without reward, but not easily replicated. You must make that next trek from the bottom once more.

The seclusion is as physical as it is mental, given the challenges a global pandemic puts us in. Gone are the meetups, the watercooler moments, the overheard new thing. It was hard enough to ask for help when I could physically tap someone on the shoulder and interrupt their flow. Strangely, it feels more difficult to strike up a call or chat when I’m stuck. Everyone is at the same time a click and a mountain away.

I’ve learned to push through this tendency to seclude and embrace my teammates’ talent. Where I used to enjoy taking a heads-down day to research a problem, I now try to shareout in nearer-to-real-time my findings. The feedback loop is tighter. I’ve adjusted the internal clock that tells me when I am spending too much time on a problem. The team exists to help one another. We’ve set aside time to pair program, mob, and demo. These plans are not without occasional setbacks, however.

Or the time when we got stuck on a bug for 4 hours, only to have fresh eyes glance at the stack trace and find a new path in the span of 15 seconds.

Our more collaborative patterns create a union of skillsets too. We combine arcs of knowledge across the tech we need. We can unblock each other faster, like long-haul truckers tag-teaming a journey. Shared understanding helps us retain context and communicate with less writing. Working more closely on even the mundane has led to change. For example, that engineer that gives me regex tips every time? Where I once bristled, or leaned into their experience, gave way to preemption. “I don’t know how to do that” turned into better and better ideas where to take my first steps. I’d expanded the circumference of my skillset a teensy bit more, journeyed a bit up a new mountain, a guide to help me see the trailhead.

I still walk alone sometimes, and that’s where I can do some of my best work. But I have a better awareness of what I don’t know, and a working realization that my team can go further together than one of us individually. I fret less at the peaks I haven’t explored yet, and am more eager than ever to ask others if they know what’s over there.

The post “I Don’t Know” appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

Three Things You Didn’t Know About AVIF

AVIF, the file format based on the AV1 video codec, is the latest addition to the next-gen image formats. Early reports and comparisons show good results compared to JPEG and WebP. However, even if browser support is good, AVIF is still on the bleeding edge in regards to encoding and decoding. Encoding, decoding, settings and parameters has been well discussed elsewhere. 

No doubt, AVIF images generate a smaller payload and are nice looking. In this post, we’ll take a closer look at issues to be aware or before you go all in on AVIF.

1. WebP is Better for Thumbnails

One interesting observation is that for small dimension images, WebP will produce lighter payload images than AVIF.

It’s probably possible to explain why, and tune the encoder to address this case. However, that is not an option for most people. Most people would probably rely on an image optimizer like squoosh.app or an image CDN like ImageEngine. The below comparison uses exactly these two alternatives for AVIF conversion. 

We see that WebP will generally produce images with a higher file size than AVIF. On larger dimension images, ImageEngine performs significantly better than squoosh.app.

Now, to the interesting observation. On images around 100px × 100px squoosh.app passes ImageEngine on effectiveness, but then also WebP catches up and for a 80px x 80px image. WebP is actually the most effective image measured in file size. 

The test performs relatively consistently on different types of images. For this illustration, this image from Picsum is used.

Pixels Original JPEG (bytes) Optimized WebP (bytes) ImageEngine AVIF (bytes) squoosh.app AVIF (bytes)
50 1,475 598 888 687
80 2,090 1,076 1,234 1,070
110 3,022 1,716 1,592 1,580
150 4,457 2,808 2,153 2,275
170 5,300 3,224 2,450 2,670
230 7,792 4,886 3,189 3,900
290 10,895 6,774 4,056 5,130

2. AVIF Might Not Be the Best for Product Images with High Entropy

Typically, a product page consists of images of the product, and when a user’s mouse hovers over or clicks on the product image, it zooms in to offer a closer look at the details.

It is worth noting that AVIF will in certain cases reduce the level of detail, or perceived sharpness, of the image when zoomed in. Especially on a typical product image where the background is blurred or has low entropy while foreground, and product, has more detail and possibly higher entropy.

Below is a zoomed in portion of a bigger product image (JPEG, AVIF) which clearly illustrates the difference between a regularly optimized JPEG versus an AVIF image optimized by squoosh.app.

The AVIF is indeed much lighter than the JPEG, but in this case the trade off between visual quality and lower file size has gone too far. This effect will not be as perceptible for all types of images, and therefore will be difficult to proactively troubleshoot in an automated build process that relies on responsive images syntax for format selection.

Moreover, unlike JPEG, AVIF does not support progressive rendering. For a typical product detail page, progressive rendering might provide a killer feature to improve key metrics like Largest Contentful Paint and other Core Web Vitals metrics. Even if a JPEG takes a little bit longer time to download due to its larger file size compared to AVIF, chances are that it will start rendering sooner than an AVIF thanks to its progressive rendering mechanism. This case is well illustrated by this video from Jake Achibald.

3. JPEG 2000 is Giving AVIF Tough Competition

The key selling point of AVIF is its extremely low file size relative to an acceptable visual image quality. Early blogs and reports have been focusing on this. However, JPEG2000 (or JP2) may in some cases be a better tool for the job. JPEG2000 is a relatively old file format and does not get the same level of attention as AVIF, even if the Apple side of the universe already supports JPEG2000.

To illustrate, let’s look at this adorable puppy. The AVIF file size optimized by squoosh.app is 27.9 KB with default settings. Converting the image to JPEG2000, again using ImageEngine, the file size is 26.7 KB. Not much difference, but enough to illustrate the case.

What about the visual quality? DSSIM is a popular way to compare how visually similar an image is to the original image. The DSSIM metric compares the original image to a converted file, with a lower value indicating better quality. Losslessly converting the AVIF and JPEG2000 version to PNG, the DSSIM score is like this:

DSSIM (0 = equal to original) Bytes
JPEG2000 0.019 26.7 KB
AVIF 0.012 27.9 KB

AVIF has slightly better DSSIM but hardly visible to the human eye.

Right Tool for the Job

The key takeaway from this article is that AVIF is hardly the “silver bullet,” or the one image format to rule them all. First of all, it is still very early in the development of both encoders and decoders. In addition, AVIF is yet another format to manage. Like Jake Archibald also concludes in his article, offering 3+ versions of each image on your webpage is a bit of a pain unless the entire workflow (resize, compress, convert, select, deliver) is all automated.

Also, like we’ve seen, just because a browser supports AVIF, it doesn’t mean that it is the best choice for your users.

Using responsive images and adding AVIF to the list of image formats to pre-create is better than not considering AVIF at all. A potential challenge is that the browser will then pick AVIF if it’s supported regardless of whether AVIF is the right tool or not.

However, using an image CDN like ImageEngine, will to a greater extent be able to dynamically choose between supported formats and make a qualified guess whether WEBP, JPEG2000 or AVIF will give the best user experience. Using an image CDN to automate the image optimization process will take into account browser compatibility, image payload size and visual quality.

The post Three Things You Didn’t Know About AVIF appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , ,

Building Custom Data Importers: What Engineers Need to Know

Importing data is a common pain-point for engineering teams. Whether its importing CRM data, inventory SKUs, or customer details, importing data into various applications and building a solution for this is a frustrating experience nearly every engineer can relate to. Data import, as a critical product experience is a huge headache. It reduces the time to value for customers, strains internal resources, and takes valuable development cycles away from developing key, differentiating product features.

Frequent error messages end-users receive when importing data. Why do we expect customers to fix this themselves?

Data importers, specifically CSV importers, haven’t been treated as key product features within the software, and customer experience. As a result, engineers tend to dedicate an exorbitant amount of effort creating less-than-ideal solutions for customers to successfully import their data.

Engineers typically create lengthy, technical documentation for customers to review when an import fails. However, this doesn’t truly solve the issue but instead offsets the burden of a great experience from the product to an end-user.

In this article, we’ll address the current problems with importing data and discuss a few key product features that are necessary to consider if you’re faced with a decision to build an in-house solution.

Importing data is typically frustrating for anyone involved at a data-led company. Simply put, there has never been a standard for importing customer data. Until now, teams have deferred to CSV templates, lengthy documentation, video tutorials, or buggy in-house solutions to allow users the ability to import spreadsheets. Companies trying to import CSV data can run into a variety of issues such as:

  • Fragmented data: With no standard way to import data, we get emails going back and forth with attached spreadsheets that are manually imported. As spreadsheets get passed around, there are obvious version control challenges. Who made this change? Why don’t these numbers add up as they did in the original spreadsheet? Why are we emailing spreadsheets containing sensitive data?
  • Improper formatting: CSV import errors frequently occur when formatting isn’t done correctly. As a result, companies often rely on internal developer resources to properly clean and format data on behalf of the client — a process that can take hours per customer, and may lead to churn anyway. This includes correcting dates or splitting fields that need to be healed prior to importing.
  • Encoding errors: There are plenty of instances where a spreadsheet can’t be imported when it’s not improperly encoded. For example, a company may need a file to be saved with UTF-8 encoding (the encoding typically preferred for email and web pages) in order to then be uploaded properly to their platform. Incorrect encoding can result in a lengthy chain of communication where the customer is burdened with correcting and re-importing their data.
  • Data normalization: A lack of data normalization results in data redundancy and a never-ending string of data quality problems that make customer onboarding particularly challenging. One example includes formatting email addresses, which are typically imported into a database, or checking value uniqueness, which can result in a heavy load on engineers to get the validation working correctly.

Remember building your first CSV importer?

When it comes down to creating a custom-built data importer, there are a few critical features that you should include to help improve the user experience. (One caveat – building a data importer can be time-consuming not only to create but also maintain – it’s easy to ensure your company has adequate engineering bandwidth when first creating a solution, but what about maintenance in 3, 6, or 12 months?)

A preview of Flatfile Portal. It integrates in minutes using a few lines of JavaScript.

Data mapping

Mapping or column-matching (they are often used interchangeably) is an essential requirement for a data importer as the file import will typically fail without it. An example is configuring your data model to accept contact-level data. If one of the required fields is “address” and the customer who is trying to import data chooses a spreadsheet where the field is labeled “mailing address,” the import will fail because “mailing address” doesn’t correlate with “address” in a back-end system. This is typically ‘solved’ by providing a pre-built CSV template for customers, who then have to manipulate their data, effectively increasing time-to-value during a product experience. Data mapping needs to be included in the custom-built product as a key feature to retain data quality and improve the customer data onboarding experience.

Auto-column matching CSV data is the bread and butter of Portal, saving massive amounts of time for customers while providing a delightful import experience.

Data validation

Data validation, which checks if the data matches an expected format or value, is another critical feature to include in a custom data importer. Data validation is all about ensuring the data is accurate and is specific to your required data model. For example, if special characters can’t be used within a certain template, error messages can appear during the import stage. Having spreadsheets with hundreds of rows containing validation errors results in a nightmare for customers, as they’ll have to fix these issues themselves, or your team, which will spend hours on end cleaning data. Automatic data validators allow for streamlining of healing incoming data without the need for a manual review.

We built Data Hooks into Portal to quickly normalize data on import. A perfect use-case would be validating email uniqueness against a database.

Data parsing

Data parsing is the process of taking an aggregation of information (in a spreadsheet) and breaking it into discrete parts. It’s the separation of data. In a custom-built data importer, a data parsing feature should not only have the ability to go from a file to an array of discrete data but also streamline the process for customers.

Data transformation

Data transformation means making changes to imported data as it’s flowing into your system to meet an expected or desired value. Rather than sending data back to users with an error message for them to fix, data transformation can make small, systematic tweaks so that the users’ data is more usable in your backend. For example, when transferring a task list, prioritization data could be transformed into a different value, such as numbers instead of labels.

Data Hooks normalize imported customer data automatically using validation rules set in the Portal JSON config. These highly adaptable hooks can be worked to auto-validate nearly any incoming customer data.

We’ve baked all of the above features into Portal, our flagship CSV importer at Flatfile. Now that we’ve reviewed some of the must-have features of a data importer, the next obvious question for an engineer building an in-house importer is typically… should they?

Engineering teams that are taking on this task typically use custom or open source solutions, which may not adhere to specific use-cases. Building a comprehensive data importer also brings UX challenges when building a UI and maintaining application code to handle data parsing, normalization, and mapping. This is prior to considering how customer data requirements may change in future months and the ramifications of maintaining a custom-built solution.

Companies facing data import challenges are now considering integrating a pre-built data importer such as Flatfile Portal. We’ve built Portal to be the elegant import button for web apps. With just a few lines of JavaScript, Portal can be implemented alongside any data model and validation ruleset, typically in a few hours. Engineers no longer need to dedicate hours cleaning up and formatting data, nor do they need to custom build a data importer (unless they want to!). With Flatfile, engineers can focus on creating product-differentiating features, rather than work on solving spreadsheet imports.

Importing data is wrought with challenges and there are several critical features necessary to include when building a data importer. The alternative to a custom-built solution is to look for a pre-built data importer such as Portal.

Flatfile’s mission is to remove barriers between humans and data. With AI-assisted data onboarding, they eliminate repetitive work and make B2B data transactions fast, intuitive, and error-free. Flatfile automatically learns how imported data should be structured and cleaned, enabling customers and teams to spend more time using their data instead of fixing it. Flatfile has transformed over 300 million rows of data for companies like ClickUp, Blackbaud, Benevity, and Toast. To learn more about Flatfile’s products, Portal and Concierge, visit flatfile.io.

The post Building Custom Data Importers: What Engineers Need to Know appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , ,