Web Standards

Write HTML, the HTML Way (Not the XHTML Way)

Css Tricks - Mon, 03/21/2022 - 10:08am

You may not use XHTML (anymore), but when you write HTML, you may be more influenced by XHTML than you think. You are very likely writing HTML, the XHTML way.

What is the XHTML way of writing HTML, and what is the HTML way of writing HTML? Let’s have a look.

HTML, XHTML, HTML

In the 1990s, there was HTML. In the 2000s, there was XHTML. Then, in the 2010s, we switched back to HTML. That’s the simple story.

You can tell by the rough dates of the specifications, too: HTML “1” 1992, HTML 2.0 1995, HTML 3.2 1997, HTML 4.01 1999; XHTML 1.0 2000, XHTML 1.1 2001; “HTML5” 2007.

XHTML became popular when everyone believed XML and XML derivatives were the future. “XML all the things.” For HTML, this had a profound effect: The effect that we learned to write it the XHTML way.

The XHTML way of writing HTML

The XHTML way is well-documented, because XHTML 1.0 describes in great detail in its section on “Differences with HTML 4”:

  • Documents must be well-formed.
  • Element and attribute names must be in lower case.
  • For non-empty elements, end tags are required.
  • Attribute values must always be quoted.
  • Attribute minimization is not supported.
  • Empty elements need to be closed.
  • White space handling in attribute values is done according to XML.
  • Script and style elements need CDATA sections.
  • SGML exclusions are not possible.
  • The elements with id and name attributes, like a, applet, form, frame, iframe, img, and map, should only use id.
  • Attributes with pre-defined value sets are case-sensitive.
  • Entity references as hex values must be in lowercase.

Does this look familiar? With the exception of marking CDATA content, as well as dealing with SGML exclusions, you probably follow all of these rules. All of them.

Although XHTML is dead, many of these rules have never been questioned again. Some have even been elevated to “best practices” for HTML.

That is the XHTML way of writing HTML, and its lasting impact on the field.

The HTML way of writing HTML

One way of walking us back is to negate the rules imposed by XHTML. Let’s actually do this (without the SGML part, because HTML isn’t based on SGML anymore):

  • Documents may not be well-formed.
  • Element and attribute names may not be in lower case.
  • For non-empty elements, end tags are not always required.
  • Attribute values may not always be quoted.
  • Attribute minimization is supported.
  • Empty elements don’t need to be closed.
  • White space handling in attribute values isn’t done according to XML.
  • Script and style elements don’t need CDATA sections.
  • The elements with id and name attributes may not only use id.
  • Attributes with pre-defined value sets are not case-sensitive.
  • Entity references as hex values may not only be in lowercase.

Let’s remove the esoteric things; the things that don’t seem relevant. This includes XML whitespace handling, CDATA sections, doubling of name attribute values, the case of pre-defined value sets, and hexadecimal entity references:

  • Documents may not be well-formed.
  • Element and attribute names may not be in lowercase.
  • For non-empty elements, end tags are not always required.
  • Attribute values may not always be quoted.
  • Attribute minimization is supported.
  • Empty elements don’t need to be closed.

Peeling away from these rules, this looks a lot less like we’re working with XML, and more like working with HTML. But we’re not done yet.

“Documents may not be well-formed” suggests that it was fine if HTML code was invalid. It was fine for XHTML to point to wellformedness because of XML’s strict error handling. But while HTML documents work even when they contain severe syntax and wellformedness issues, it’s neither useful for the professional — nor our field — to use and abuse this resilience. (I’ve argued this case before in my article, “In Critical Defense of Frontend Development.”)

The HTML way would therefore not suggest “documents may not be well-formed.” It would also be clear that not only end, but also start tags aren’t always required. Rephrasing and reordering, this is the essence:

  • Start and end tags are not always required.
  • Empty elements don’t need to be closed.
  • Element and attribute names may be lower or upper case.
  • Attribute values may not always be quoted.
  • Attribute minimization is supported.
Examples

How does this look like in practice? For start and end tags, be aware that many tags are optional. A paragraph and a list, for example, are written like this in XHTML:

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p> <ul> <li>Praesent augue nisl</li> <li>Lobortis nec bibendum ut</li> <li>Dictum ac quam</li> </ul>

In HTML, however, you can write them using only this code (which is valid):

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <ul> <li>Praesent augue nisl <li>Lobortis nec bibendum ut <li>Dictum ac quam </ul>

Developers also learned to write void elements, like so:

<br />

This is something XHTML brought to HTML, but as the slash has no effect on void elements, you only need this:

<br>

In HTML, you can also just write everything in all caps:

<A HREF="https://css-tricks.com/">CSS-Tricks</A>

It looks like you’re yelling and you may not like it, but it’s okay to write it like this.

When you want to condense that link, HTML offers you the option to leave out certain quotes:

<A HREF=https://css-tricks.com/>CSS-Tricks</A>

As a rule of thumb, when the attribute value doesn’t contain a space or an equal sign, it’s usually fine to drop the quotes.

Finally, HTML–HTML — not XHTML–HTML — also allows to minimize attributes. That is, instead of marking an input element as required and read-only, like this:

<input type="text" required="required" readonly="readonly">

You can minimize the attributes:

<input type="text" required readonly>

If you’re not only taking advantage of the fact that the quotes aren’t needed, but that text is the default for the type attribute here (there are more such unneeded attribute–value combinations), you get an example that shows HTML in all its minimal beauty:

<input required readonly> Write HTML, the HTML way

The above isn’t a representation of where HTML was in the 90s. HTML, back then, was loaded with <table> elements for layout, packed with presentational code, largely invalid (as it’s still today), with wildly varying user agent support. Yet it’s the essence of what we would have wanted to keep if XML and XHTML hadn’t come around.

If you’re open to a suggestion of what a more comprehensive, contemporary way of writing HTML could look like, I have one. (HTML is my main focus area, so I’m augmenting this by links to some of my articles.)

  1. Respect syntax and semantics.
  2. Use the options HTML gives you, as long as you do so consistently.
    • Remember that element and attribute names may be lowercase or uppercase.
  3. Keep use of HTML to the absolute minimum
    • Remember that presentational and behavioral markup is to be handled by CSS and JavaScript instead.
    • Remember that start and end tags are not always required.
    • Remember that empty elements don’t need to be closed.
    • Remember that some attributes have defaults that allow these attribute–value pairs to be omitted.
    • Remember that attribute values may not always be quoted.
    • Remember that attribute minimization is supported.

It’s not a coincidence that this resembles the three ground rules for HTML, that it works with the premise of a smaller payload also leading to faster sites, and that this follows the school of minimal web development. None of this is new — our field could merely decide to rediscover it. Tooling is available, too: html-minifier is probably the most established and able to handle all HTML optimizations.

You’ve learned HTML the XHTML way. HTML isn’t XHTML. Rediscover HTML, and help shape a new, modern way of writing HTML — which acknowledges, but isn’t necessarily based on XML.

Write HTML, the HTML Way (Not the XHTML Way) originally published on CSS-Tricks. You should get the newsletter.

How To Empathize With Your Users

Usability Geek - Wed, 03/16/2022 - 4:48am
Empathizing with your users creates successful products. So here are some design tools and tips to help you strengthen your user empathy and enhance your UX research. This guide will help you know...
Categories: Web Standards

CSS-Tricks is joining DigitalOcean!

Css Tricks - Tue, 03/15/2022 - 2:18am

Hey hey!

I’ve got a big announcement to make here. (Where’s my gong? I feel like this really needs a good gong hit.)

CSS-Tricks, this very website you’re looking at, has been acquired by DigitalOcean!

You can hear from them directly on the DigitalOcean blog as well.

This will be the most fun if we have a conversation about it, so allow me to kick it off. I’ll pretend to be you at first and then you can be you in the comments.

Hey, congrats!

Thanks! 

DigitalOcean? Aren’t they like a… web host?

They are! A very good one. You can build anything on DigitalOcean infrastructure (probably best to think of them as a cloud computing platform that has tools ranging from servers to managed Kubernetes). One thing I think is particularly cool is their new App Platform which to me feels extra aligned with front-end developers like me. We’ve covered that before. Also, their whole concept of Droplets (super simple servers that are quick to spin up) has been transformative in the industry.  But more importantly about this acquisition… have you seen their DigitalOcean Community site? It’s loaded with top-notch developer education. DigitalOcean has been super committed to that for a long time, and to me makes this a very natural and well-suited move.

What happens to CSS-Tricks?

The site and content is staying right here. DigitalOcean is committed to continuing to produce high-quality content on front-end development and tending to the trove of content that exists here already. 

Will you still be running CSS-Tricks?

I will be working with the DigitalOcean team as an advisor as we transition CSS-Tricks to DigitalOcean’s management, and will then step back to focus on my other projects. 

Why now?

When I started CSS-Tricks in 2007, I couldn’t have imagined how much it would grow. I wanted it to grow, that was the plan, but now it’s a far bigger job than any one person can do. That, I was never ready for. 

Let me take a quick moment to give some thanks here. I had the incredible help of Geoff as lead editor, sponsor wrangler, and site manager. Robin turned the newsletter into the must-read industry rag it is now. It’s a family business as well! My wife Miranda helped with the books, working with authors, and her guidance on running the site as a proper publication has led the site where it is. I literally couldn’t have done it without any one of them. And of course, the incredible group of authors, with a special shout out to Sarah, a long-time staff writer and friend.

A small but mighty team, indeed. And that’s the thing. CSS-Tricks deserves more human muscle behind it than I’ve been able to provide for it. That’s where DigitalOcean comes in. That’s the “why now.” They have the resources to put behind CSS-Tricks, and the motivation to do so. I fully trust them to do it, as they’ve been successfully doing it themselves for a long time.

OK! Your turn! If you have any thoughts or questions, feel free to comment below. We’ll read them all and publish anything useful and constructive.

CSS-Tricks is joining DigitalOcean! originally published on CSS-Tricks. You should get the newsletter.

Say Hello to selectmenu, a Fully Style-able select Element

Css Tricks - Thu, 03/03/2022 - 5:35am

I want to introduce you to a new, experimental form control called <selectmenu>. We’ll get deep into it, including how much easier it is to style than a traditional <select> element. But first, let’s fill in some context about why something like <selectmenu> is needed in the first place, as it’s still evolving and in development.

Ask any web developer what they think is missing from the web platform today, chances are the ability to style form controls will be on their list. In fact, form styling was voted as one of the top-10 missing things in the State of CSS Survey in 2020. It was then further surveyed by Greg Whitworth who showed that <select> was the control web developers were having the most problems styling with CSS.

While it’s relatively easy to style the appearance of the button part of a <select> (the thing you see in the page when the popup is closed), it’s almost impossible to style the options (the thing you see when the popup is open), let alone add more content within the popup.

The default UI for a <select> element in Safari

As a result, design systems and component libraries have been rolling out their own selects, made from scratch using custom HTML markup, CSS, and often a lot of JavaScript, in order to have something that integrates nicely with the other components.

Unfortunately, doing so correctly with the right accessibility semantics, keyboard support, and popup positioning is not easy. Web developers have poured hours and hours over the years, trying to solve the same problems over and over, and there are many inaccessible selects out there.

It’s about time we got a properly style-able built-in <select> so we don’t have to write this code ever again!

The Open UI initiative

Open UI is a group of developers, designers, and browser implementers who set out to solve this exact problem, and while they’re at it, tackle other missing controls too.

The purpose of Open UI is to eventually make it possible for web developers to style and extend built-in UI controls (this includes <select>, but dropdowns, checkboxes, radio buttons, and others too). To achieve this, they produce specifications for how these controls should be implemented in the web platform as well as the accessibility requirements they should address.

The project is still in its infancy, but things are moving fast and, as we’ll see below, exciting things are already happening.

You can join the group and participate in the meetings, research, and specification efforts.

The <selectmenu> control

Based on the Open UI’s <select> proposal, the implementation of a new <selectmenu> control has started in Chromium! The work is done by the Microsoft Edge team, in collaboration with the Google Chrome team. It’s even already available in Chromium-based browsers by enabling the “Experimental Web Platform features” flag in the about:flags page.

<selectmenu> is a new built-in control that provides an option selection user experience, just like <select>, with a button showing the selected value label, a popup that appears when that button is clicked, and a list of options that get displayed.

Why a new name?

Why not just replace the existing <select> control? The name “selectmenu” started as a working name, but it seems to have stuck so far, and no one has come up with anything better yet.

More importantly, the existing <select> control has been used on the web for a very long time. As such, it can probably never be changed in any significant way without causing major compatibility issues.

So, the plan (and remember this is all still very experimental) is for <selectmenu> to be a new control, independent from <select>.

Try it out today

This isn’t ready for production use yet, but if you’re as excited as I am about using it, here’s how:

  1. Open a Canary version of a Chromium-based browser (Chrome, Edge).
  2. Switch the “Experimental Web Platform features” flag in the about:flags page and restart.
  3. Replace any <select> by <selectmenu> in a web page!

That’s it! It won’t do much by default, but as we’ll see later, you’ll be able to style and extend the control quite extensively with this one tag name change.

We love feedback!

Before we go into how to use the control, if you do use it, the Open UI group and people working on the implementation in Chromium would love to hear your feedback if you have any.

By being an early tester, you can actively help them make the control better for everyone. So, if you encounter bugs or limitations with the design of the control, please send your feedback by creating an issue on the Open UI GitHub repository!

And now, let’s talk about how the control works.

The anatomy of a <selectmenu> control

Because the various parts of the selectmenu can be styled, it’s important to first understand its internal anatomy.

  • <selectmenu> is the root element that contains the button and listbox.
  • <button> is the element that triggers the visibility of the listbox.
  • <selected-value> is the element that displays the value of the currently selection option (optional). Note that this part does not necessarily have to be placed inside the <button> part.
  • <listbox> is the wrapper that contains the <option>s and <optgroup>s.
  • <optgroup> groups s together with an optional label.
  • <option> represents the potential value that can be chosen by the user. There can be one or more.
Default behavior

The default behavior of the <selectmenu> control mimics the behavior of the <select> control. You can use it just like a native <select>, with the following minimal markup.

<selectmenu> <option>Option 1</option> <option>Option 2</option> <option>Option 3</option> </selectmenu>

When doing so, the default <button>, <selected-value>, and <listbox >are created for you.

Styling parts of the control

This is where things become interesting! One way to style the control to match your requirements is to use the CSS ::part() pseudo-element to select the different parts within the control’s anatomy that you wish to style.

Consider the following example where ::part() is used to style the button and the listbox parts:

<style> .my-select-menu::part(button) { color: white; background-color: #f00; padding: 5px; border-radius: 5px; } .my-select-menu::part(listbox) { padding: 10px; margin-top: 5px; border: 1px solid red; border-radius: 5px; } </style> <selectmenu class="my-select-menu"> <option>Option 1</option> <option>Option 2</option> <option>Option 3</option> </selectmenu>

The above example results in the following style:

::part() can be used to style the <button>, <selected-value>, and <listbox> parts of the control.

Use your own markup

If the above isn’t enough for your needs, you can customize the control much more by providing your own markup to replace the default one, and extend or re-order the parts.

A <selectmenu> has named slots that can be referenced to replace the default parts. For example, to replace the default button with your own, you can do the following:

<style> .my-custom-select [slot='button'] { display: flex; align-content: center; } .my-custom-select button { padding: 5px; border: none; background: #f06; border-radius: 5px 0 0 5px; color: white; font-weight: bold; } .my-custom-select .label { padding: 5px; border: 1px solid #f06; border-radius: 0 5px 5px 0; } </style> <selectmenu class="my-custom-select"> <div slot="button"> <button behavior="button">Open</button> <span class="label">Choose an option</span> </div> <option>Option 1</option> <option>Option 2</option> <option>Option 3</option> </selectmenu>

The slot="button" attribute on the outer <div> tells the <selectmenu> to replace its default button with the contents of the <div>.

The behavior="button" attribute on the inner <button> tells the browser that this element is what we want to use as the new button. The browser will automatically apply all the click and keyboard handling behavior to this element as well as the appropriate accessibility semantics.

The above code snippet results in the following style:

Note that the slot and behavior attributes can also be used on the same element.

You can replace the default listbox part in a similar fashion:

<style> .my-custom-select [popup] { width: 300px; display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 10px; padding: 10px; box-shadow: none; margin: 10px 0; border: 1px solid; background: #f7f7f7; } </style> <selectmenu class="my-custom-select"> <div slot="listbox"> <div popup="popup" behavior="listbox"> <option>Option 1</option> <option>Option 2</option> <option>Option 3</option> <option>Option 4</option> <option>Option 5</option> </div> </div> </selectmenu>

Interestingly, the <div popup> used here is also being proposed by Open UI and implemented in Chromium at the moment.

The element with behavior="listbox" is required to be a <div popup>. Applying behavior="listbox" tells the browser to open this element when the <selectmenu> button is clicked, and the user can select <option>s inside it with mouse, arrow keys, and touch.

The above code snippet results in the following style:

Extending the markup

Not only can you replace the default parts with your own, as seen above, you can also extend the control’s markup by adding new elements. This can be useful to augment the listbox or button with extra information, or to add new functionality.

Consider the following example:

<style> .my-custom-select [slot='button'] { display: flex; align-items: center; gap: 1rem; } .my-custom-select button { border: none; margin: 0; padding: 0; width: 2rem; height: 2rem; border-radius: 50%; display: grid; place-content: center; } .my-custom-select button::before { content: '\25BC'; } .my-custom-select [popup] { padding: 0; } .my-custom-select .section { padding: 1rem 0 0; background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%); } .my-custom-select h3 { margin: 0 0 1rem 0; text-align: center; color: white; } .my-custom-select option { text-align: center; padding: 0.5rem; } </style> <selectmenu class="my-custom-select"> <div slot="button"> <span class="label">Choose a plant</span> <span behavior="selected-value" slot="selected-value"></span> <button behavior="button"></button> </div> <div slot="listbox"> <div popup="popup" behavior="listbox"> <div class="section"> <h3>Flowers</h3> <option>Rose</option> <option>Lily</option> <option>Orchid</option> <option>Tulip</option> </div> <div class="section"> <h3>Trees</h3> <option>Weeping willow</option> <option>Dragon tree</option> <option>Giant sequoia</option> </div> </div> </div> </selectmenu>

Here we’re using custom markup to wrap the list of options and create our own content as seen below:

Replacing the entire shadow DOM

Finally, and if the above wasn’t enough, you can also extend the control’s markup by replacing its default shadow DOM altogether by calling attachShadow(). For example, the demo in the previous section could be modified as follows:

<selectmenu id="my-custom-select"></selectmenu> <script> const myCustomSelect = document.querySelector('#my-custom-select') const shadow = myCustomSelect.attachShadow({ mode: 'closed' }) shadow.innerHTML = ` <style> .button-container { display: flex; align-items: center; gap: 1rem; } button { border: none; margin: 0; padding: 0; width: 2rem; height: 2rem; border-radius: 50%; display: grid; place-content: center; } button::before { content: '\\0025BC'; } [popup] { padding: 0; } .section { padding: 1rem 0 0; background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%); } h3 { margin: 0 0 1rem 0; text-align: center; color: white; } option { text-align: center; padding: 0.5rem; } option:hover { background-color: lightgrey; } </style> <div class="button-container"> <span class="label">Choose a plant</span> <span behavior="selected-value" slot="selected-value"></span> <button behavior="button"></button> </div> <div popup="popup" behavior="listbox"> <div class="section"> <h3>Flowers</h3> <option>Rose</option> <option>Lily</option> <option>Orchid</option> <option>Tulip</option> </div> <div class="section"> <h3>Trees</h3> <option>Weeping willow</option> <option>Dragon tree</option> <option>Giant sequoia</option> </div> </div> ` </script>

Written this way, the <selectmenu>‘s custom markup is fully encapsulated in its shadow DOM. The <selectmenu> can therefore be dropped into any page without risk of interference from the surrounding content’s styles.

Closing remarks

As we’ve seen, the new experimental <selectmenu> control offers a lot of flexibility when it comes to styling and even extending a traditional <select>. And it does this in all the right ways, because it’s built into the browser where accessibility and viewport-aware positioning are handled for you.

Open UI has more documentation about <selectmenu>, and if you want to see more code showing how to use the <selectmenu>, here are a few demos as well.

Again, this is work in progress and will most certainly change as a result of feedback received by the Open UI group.

I can’t wait to see specifications start to appear in HTML and CSS standard bodies, and for the implementation to become more stable, as well as see other browser engines getting interested in this. You can help make this happen! Testing the control, reporting issues, or getting involved are all great ways to help push this effort forward.

Say Hello to selectmenu, a Fully Style-able select Element originally published on CSS-Tricks. You should get the newsletter.

Build Membership Businesses with Memberful

Css Tricks - Thu, 03/03/2022 - 5:33am

(This is a sponsored post.)

What would your business be like if you sold memberships? It might be more than fun to think about, in fact, it might just be transformative. With membership, would you include little add-ons or perks for your biggest fans? Or could it become the entire core of what you do? Likewise, what might you help your clients build?

Whatever you decide to do (or perhaps what a client has hired you to do), you can get it done with Memberful. It’s the best software for building membership businesses, used by the biggest creators on the web.

Memberful is for developers.

You have a goal in mind: to build a great member-powered web experience. But you’re a developer and want to build how you want to build. Good. Memberful is here to support that.

Memberful maintains a full-featured GraphQL API (complete with GraphiQL explorer), webhooks, and OAth Single Sign-on to make it easy to integrate seamlessly with any stack.

Your main site in Rails? No problem. You wanna do a Jamstack thing? Perfect. Do you have a React-powered app through and through? That’ll work.

You don’t have to build entirely from scratch.

For example, Memberful maintains a best-in-class WordPress plugin that easily gates content and adds membership to your website. So if you happen to use WordPress as your CMS, or are thinking of using it to build your website, you’ve got a first-class integration to work with.

If you’re looking to add membership to your existing business, you’ll want a solution that works with your existing technology, so you can launch a new revenue stream without rebuilding your entire tech stack.

You don’t even need to build a website to use Memberful.

If you’re not looking to gate access to content on a custom website, you won’t need a website at all to use Memberful. You can use Memberful’s hosted landing page feature to sell your memberships, and then deliver your member benefits like a private podcast, protected downloads, and newsletters right through the platform — no website required.

Say you already use other tools for your business. Maybe you do all your emailing with MailChimp. Great! Memberful fully integrates with MailChimp. Is Discord your community hub? No problem, Memberful seamlessly integrates with Discord, meaning you can offer paid subscribers perks that are unique to Discord, things like special channels or access roles.

What are some common use cases for Memberful?
  • Private Podcasts
  • Subscription Newsletters
  • Selling digital goods (or physical!)
  • Building communities
  • Membership-driven educational courses

Memberful handles the hard stuff so you can focus on what you do best, while earning revenue quickly. Even advanced features like gift subscriptions, coupons, referrals, free and paid trials and more, are right there for you to take advantage of.

Who handles all the emails?

Memberful does, and you get 100% control of the brand. Yet another thing you won’t need to worry about, which can otherwise be an awful lot of work and technical debt. I’m talking transactional emails here, like signup confirmation emails, welcome emails, forgot password emails, etc.

How does the money work?

Memberful has a free plan to get your feet wet. You can build 2 subscription plans and do website integrations. The PRO plan starts at $25/month and lowers transaction fees to 4.9% down from 10% on the free plan.

Where do those subscription payments go? You connect your Stripe account, so that money goes there, which is the best-in-business payment provider. That means you can offer additional features like Apple Pay and Google Pay which are great for lowering checkout friction for potential customers.

Have questions? Memberful has always-real-human based support at all times to help you.

Build Membership Businesses with Memberful originally published on CSS-Tricks. You should get the newsletter.

7 Fresh Links on Performance For March 2022

Css Tricks - Wed, 03/02/2022 - 11:26am

I have a handful of good links to articles about performance that are burning a hole in my bookmarks folder, and wanna drop them here to share.

The new WebPageTest website design

7 Fresh Links on Performance For March 2022 originally published on CSS-Tricks. You should get the newsletter.

Ahmad Shadeed: Use Cases For CSS fit-content

Css Tricks - Tue, 03/01/2022 - 5:41am

Ahmad Shadeed covers the CSS fit-content sizing keyword. It’s useful! It just doesn’t come up super often. I find myself using min-content a lot more, like when setting up the height of a grid-template-row.

The fit-content keyword is actually closely related to min-content and max-content — it just has a little heuristic it follows that Ahmad nicely illustrates as a flow chart.

“Use Cases For CSS fit-content” by Ahmad Shadeed

My favorite use case is covered here: sizing a <figure> with fit-content, so that it neatly wraps around the <img>. That way, even if the image doesn’t fill the parent space, and it can remain block-level.

We also covered PPK’s deep dive on fit-content last year. One of the key takeaways for understanding it is knowing that is it essentially a shorthand way of writing:

.box { width: fit-content; /* ... is the same as ... */ width: auto; min-width: min-content; max-width: max-content; }

To Shared LinkPermalink on CSS-Tricks

Ahmad Shadeed: Use Cases For CSS fit-content originally published on CSS-Tricks. You should get the newsletter.

Ahmad Shadeed: Use Cases For CSS fit-content

Css Tricks - Tue, 03/01/2022 - 5:41am

Ahmad Shadeed covers the CSS fit-content sizing keyword. It’s useful! It just doesn’t come up super often. I find myself using min-content a lot more, like when setting up the height of a grid-template-row.

The fit-content keyword is actually closely related to min-content and max-content — it just has a little heuristic it follows that Ahmad nicely illustrates as a flow chart.

“Use Cases For CSS fit-content” by Ahmad Shadeed

My favorite use case is covered here: sizing a <figure> with fit-content, so that it neatly wraps around the <img>. That way, even if the image doesn’t fill the parent space, and it can remain block-level.

We also covered PPK’s deep dive on fit-content last year. One of the key takeaways for understanding it is knowing that is it essentially a shorthand way of writing:

.box { width: fit-content; /* ... is the same as ... */ width: auto; min-width: min-content; max-width: max-content; }

To Shared LinkPermalink on CSS-Tricks

Ahmad Shadeed: Use Cases For CSS fit-content originally published on CSS-Tricks. You should get the newsletter.

IE Down, Edge Up… Global Browser Usage Stats Are for Cocktail Parties and Conference Slides

Css Tricks - Mon, 02/28/2022 - 1:14pm

I enjoy articles like Hartley Charlton’s “Microsoft Edge Looks Set to Overtake Safari as World’s Second Most Popular Desktop Browser.” It’s juicy! We know these massive players in the browser market care very much about their market share, so when one passes another it’s news. Like an Olympic speed skater favored for the gold getting a bronze instead, or the like.

Microsoft Edge is now used on 9.54 percent of desktops worldwide, a mere 0.3 percent behind Apple’s Safari, which stands at 9.84 percent. Google Chrome continues to hold first place with an overwhelming 65.38 percent of the market. Mozilla Firefox takes fourth place with 9.18 percent.

In January 2021, Safari held a 10.38 percent market share and appears to be gradually losing users to rival browsers over time. If the trend continues, Apple is likely to slip to third or fourth place in the near future.

Scoping the data down even by continent is entirely different. Like in Europe, Edge has already passed Safari, but in North America, the gap is still 5%.

Source: MacRumors.com

What does it matter to you or me? Nothing, I hope. These global stats should mean very little to us, outside a little casual nerdy cocktail party chatter. Please don’t make decisions about what to support and not support based on global statistics. Put some kind of basic analytics in place on your site, get data from actual visits, and make choices on that data. That’s the only data that matters.

Alan Dávalos’ “The baseline for web development in 2022” paints a picture of what we should be supporting based again on global browser usage statistics.

Globally, IE’s current market share is under 0.5%. And even in Japan, which has a higher market share of IE compared to other countries, IE’s market share is close to 2% and has a downward tendency.

Until now we kept supporting IE due to its market share. But now, there are basically no good reasons to keep supporting IE.

Again it seems so bizarre to me that any of us would make a choice on what to support based on a global usage statistic. Even when huge players make choices, they do it based on their own data. When Google “dropped” IE 11 (they still serve a perfectly fine baseline experience), they “did the math.” WordPress, famously powering somewhere in the “a third of the whole internet” range, factored in usage of their own product.

Even if you’re building a brand new product and trying to make these choices, you’ll have analytic data soon enough, and can make future-facing support choices based on that as it rolls in.

IE Down, Edge Up… Global Browser Usage Stats Are for Cocktail Parties and Conference Slides originally published on CSS-Tricks. You should get the newsletter.

IE Down, Edge Up… Global Browser Usage Stats Are for Cocktail Parties and Conference Slides

Css Tricks - Mon, 02/28/2022 - 1:14pm

I enjoy articles like Hartley Charlton’s “Microsoft Edge Looks Set to Overtake Safari as World’s Second Most Popular Desktop Browser.” It’s juicy! We know these massive players in the browser market care very much about their market share, so when one passes another it’s news. Like an Olympic speed skater favored for the gold getting a bronze instead, or the like.

Microsoft Edge is now used on 9.54 percent of desktops worldwide, a mere 0.3 percent behind Apple’s Safari, which stands at 9.84 percent. Google Chrome continues to hold first place with an overwhelming 65.38 percent of the market. Mozilla Firefox takes fourth place with 9.18 percent.

In January 2021, Safari held a 10.38 percent market share and appears to be gradually losing users to rival browsers over time. If the trend continues, Apple is likely to slip to third or fourth place in the near future.

Scoping the data down even by continent is entirely different. Like in Europe, Edge has already passed Safari, but in North America, the gap is still 5%.

Source: MacRumors.com

What does it matter to you or me? Nothing, I hope. These global stats should mean very little to us, outside a little casual nerdy cocktail party chatter. Please don’t make decisions about what to support and not support based on global statistics. Put some kind of basic analytics in place on your site, get data from actual visits, and make choices on that data. That’s the only data that matters.

Alan Dávalos’ “The baseline for web development in 2022” paints a picture of what we should be supporting based again on global browser usage statistics.

Globally, IE’s current market share is under 0.5%. And even in Japan, which has a higher market share of IE compared to other countries, IE’s market share is close to 2% and has a downward tendency.

Until now we kept supporting IE due to its market share. But now, there are basically no good reasons to keep supporting IE.

Again it seems so bizarre to me that any of us would make a choice on what to support based on a global usage statistic. Even when huge players make choices, they do it based on their own data. When Google “dropped” IE 11 (they still serve a perfectly fine baseline experience), they “did the math.” WordPress, famously powering somewhere in the “a third of the whole internet” range, factored in usage of their own product.

Even if you’re building a brand new product and trying to make these choices, you’ll have analytic data soon enough, and can make future-facing support choices based on that as it rolls in.

IE Down, Edge Up… Global Browser Usage Stats Are for Cocktail Parties and Conference Slides originally published on CSS-Tricks. You should get the newsletter.

Web Component Pseudo-Classes and Pseudo-Elements are Easier Than You Think

Css Tricks - Mon, 02/28/2022 - 5:37am

We’ve discussed a lot about the internals of using CSS in this ongoing series on web components, but there are a few special pseudo-elements and pseudo-classes that, like good friends, willingly smell your possibly halitotic breath before you go talk to that potential love interest. You know, they help you out when you need it most. And, like a good friend will hand you a breath mint, these pseudo-elements and pseudo-classes provide you with some solutions both from within the web component and from outside the web component — the website where the web component lives.

I’m specifically referring to the ::part and ::slotted pseudo-elements, and the :defined, :host, and :host-context pseudo-classes. They give us extra ways to interact with web components. Let’s examine them closer.

Article series The ::part pseudo-element

::part, in short, allows you to pierce the shadow tree, which is just my Lord-of-the-Rings-y way to say it lets you style elements inside the shadow DOM from outside the shadow DOM. In theory, you should encapsulate all of your styles for the shadow DOM within the shadow DOM, i.e. within a <style> element in your <template> element.

So, given something like this from the very first part of this series, where you have an <h2> in your <template>, your styles for that <h2> should all be in the <style> element.

<template id="zprofiletemplate"> <style> h2 { font-size: 3em; margin: 0 0 0.25em 0; line-height: 0.8; } /* other styles */ </style> <div class="profile-wrapper"> <div class="info"> <h2> <slot name="zombie-name">Zombie Bob</slot> </h2> <!-- other zombie profile info --> </div> </template>

But sometimes we might need to style an element in the shadow DOM based on information that exists on the page. For instance, let’s say we have a page for each zombie in the undying love system with matches. We could add a class to profiles based on how close of a match they are. We could then, for instance, highlight a match’s name if he/she/it is a good match. The closeness of a match would vary based on whose list of potential matches is being shown and we won’t know that information until we’re on that page, so we can’t bake the functionality into the web component. Since the <h2> is in the shadow DOM, though, we can’t access or style it from outside the shadow DOM meaning a selector of zombie-profile h2 on the matches page won’t work.

But, if we make a slight adjustment to the <template> markup by adding a part attribute to the <h2>:

<template id="zprofiletemplate"> <style> h2 { font-size: 3em; margin: 0 0 0.25em 0; line-height: 0.8; } /* other styles */ </style> <div class="profile-wrapper"> <div class="info"> <h2 part="zname"> <slot name="zombie-name">Zombie Bob</slot> </h2> <!-- other zombie profile info --> </div> </template>

Like a spray of Bianca in the mouth, we now have the superpowers to break through the shadow DOM barrier and style those elements from outside of the <template>:

/* External stylesheet */ .high-match::part(zname) { color: blue; } .medium-match::part(zname) { color: navy; } .low-match::part(zname) { color: slategray; } CodePen Embed Fallback

There are lots of things to consider when it comes to using CSS ::part. For example, styling an element inside of a part is a no-go:

/* frowny-face emoji */ .high-match::part(zname) span { ... }

But you can add a part attribute on that element and style it via its own part name.

What happens if we have a web component inside another web component, though? Will ::part still work? If the web component appears in the page’s markup, i.e. you’re slotting it in, ::part works just fine from the main page’s CSS.

<zombie-profile class="high-match"> <img slot="profile-image" src="https://assets.codepen.io/1804713/leroy.png" /> <span slot="zombie-name">Leroy</span> <zombie-details slot="zdetails"> <!-- Leroy's details --> </zombie-details> </zombie-profile>

But if the web component is in the template/shadow DOM, then ::part cannot pierce both shadow trees, just the first one. We need to bring the ::part into the light… so to speak. We can do that with an exportparts attribute.

To demonstrate this we’ll add a “watermark” behind the profiles using a web component. (Why? Believe it or not this was the least contrived example I could come up with.) Here are our templates: (1) the template for <zombie-watermark>, and (2) the same template for <zombie-profile> but with added a <zombie-watermark> element on the end.

<template id="zwatermarktemplate"> <style> div { text-transform: uppercase; font-size: 2.1em; color: rgb(0 0 0 / 0.1); line-height: 0.75; letter-spacing: -5px; } span { color: rgb( 255 0 0 / 0.15); } </style> <div part="watermark"> U n d y i n g L o v e U n d y i n g L o v e U n d y i n g L o v e <span part="copyright">©2 0 2 7 U n d y i n g L o v e U n L t d .</span> <!-- Repeat this a bunch of times so we can cover the background of the profile --> </div> </template> <template id="zprofiletemplate"> <style> ::part(watermark) { color: rgb( 0 0 255 / 0.1); } /* More styles */ </style> <!-- zombie-profile markup --> <zombie-watermark exportparts="copyright"></zombie-watermark> </template> <style> /* External styles */ ::part(copyright) { color: rgb( 0 100 0 / 0.125); } </style>

Since ::part(watermark) is only one shadow DOM above the <zombie-watermark>, it works fine from within the <zombie-profile>’s template styles. Also, since we’ve used exportparts="copyright" on the <zombie-watermark>, the copyright part has been pushed up into the <zombie-profile>‘s shadow DOM and ::part(copyright) now works even in external styles, but ::part(watermark) will not work outside the <zombie-profile>’s template.

CodePen Embed Fallback

We can also forward and rename parts with that attribute:

<zombie-watermark exportparts="copyright: cpyear"></zombie-watermark> /* Within zombie-profile's shadow DOM */ /* happy-face emoji */ ::part(cpyear) { ... } /* frowny-face emoji */ ::part(copyright) { ... }

Structural pseudo-classes (:nth-child, etc.) don’t work on parts either, but, at least in Safari, you can use pseudo-classes like :hover. Let’s animate the high match names a little and make them shake as they’re lookin’ for some lovin’. Okay, I heard that and agree it’s awkward. Let’s… uh… make them more, shall we say, noticeable, with a little movement.

.high::part(name):hover { animation: highmatch 1s ease-in-out; } CodePen Embed Fallback The ::slotted pseudo-element

The ::slotted CSS pseudo-element actually came up when we covered interactive web components. The basic idea is that ::slotted represents any content in a slot in a web component, i.e. the element that has the slot attribute on it. But, where ::part pierces through the shadow DOM to make a web component’s elements accessible to outside styles, ::slotted remains encapsulated in the <style> element in the component’s <template> and accesses the element that’s technically outside the shadow DOM.

In our <zombie-profile> component, for example, each profile image is inserted into the element through the slot="profile-image".

<zombie-profile> <img slot="profile-image" src="photo.jpg" /> <!-- rest of the content --> </zombie-profile>

That means we can access that image — as well as any image in any other slot — like this:

::slotted(img) { width: 100%; max-width: 300px; height: auto; margin: 0 1em 0 0; }

Similarly, we could select all slots with ::slotted(*) regardless of what element it is. Just beware that ::slotted has to select an element — text nodes are immune to ::slotted zombie styles. And children of the element in the slot are inaccessible.

The :defined pseudo-class

:defined matches all defined elements (I know, surprising, right?), both built-in and custom. If your custom element is shuffling along like a zombie avoiding his girlfriend’s dad’s questions about his “living” situation, you may not want the corpses of the content to show while you’re waiting for them to come back to life errr… load.

You can use the :defined pseudo-class to hide a web component before it’s available — or “defined” — like this:

:not(:defined) { display: none; }

You can see how :defined acts as a sort of mint in the mouth of our component styles, preventing any broken content from showing (or bad breath from leaking) while the page is still loading. Once the element’s defined, it’ll automatically appear because it’s now, you know, defined and not not defined.

I added a setTimeout of five seconds to the web component in the following demo. That way, you can see that <zombie-profile> elements are not shown while they are undefined. The <h1> and the <div> that holds the <zombie-profile> components are still there. It’s just the <zombie-profile> web component that gets display: none since they are not yet defined.

CodePen Embed Fallback The :host pseudo-class

Let’s say you want to make styling changes to the custom element itself. While you could do this from outside the custom element (like tightening that N95), the result would not be encapsulated, and additional CSS would have to be transferred to wherever this custom element is placed.

It’d be very convenient then to have a pseudo-class that can reach outside the shadow DOM and select the shadow root. That CSS pseudo-class is :host.

In previous examples throughout this series, I set the <zombie-profile> width from the main page’s CSS, like this:

zombie-profile { width: calc(50% - 1em); }

With :host, however, I can set that width from inside the web component, like this:

:host { width: calc(50% - 1em); }

In fact, there was a div with a class of .profile-wrapper in my examples that I can now remove because I can use the shadow root as my wrapper with :host. That’s a nice way to slim down the markup.

CodePen Embed Fallback

You can do descendant selectors from the :host, but only descendants inside the shadow DOM can be accessed — nothing that’s been slotted into your web component (without using ::slotted).

That said, :host isn’t a one trick zombie. It can also take a parameter, e.g. a class selector, and will only apply styling if the class is present.

:host(.high) { border: 2px solid blue; }

This allows you to make changes should certain classes be added to the custom element.

You can also pass pseudo-classes in there, like :host(:last-child) and :host(:hover).

CodePen Embed Fallback The :host-context pseudo-class

Now let’s talk about :host-context. It’s like our friend :host(), but on steroids. While :host gets you the shadow root, it won’t tell you anything about the context in which the custom element lives or its parent and ancestor elements.

:host-context, on the other hand, throws the inhibitions to the wind, allowing you to follow the DOM tree up the rainbow to the leprechaun in a leotard. Just note that at the time I’m writing this, :host-context is unsupported in Firefox or Safari. So use it for progressive enhancement.

Here’s how it works. We’ll split our list of zombie profiles into two divs. The first div will have all of the high zombie matches with a .bestmatch class. The second div will hold all the medium and low love matches with a .worstmatch class.

<div class="profiles bestmatch"> <zombie-profile class="high"> <!-- etc. --> </zombie-profile> <!-- more profiles --> </div> <div class="profiles worstmatch"> <zombie-profile class="medium"> <!-- etc. --> </zombie-profile> <zombie-profile class="low"> <!-- etc. --> </zombie-profile> <!-- more profiles --> </div>

Let’s say we want to apply different background colors to the .bestmatch and .worstmatch classes. We are unable to do this with just :host:

:host(.bestmatch) { background-color: #eef; } :host(.worstmatch) { background-color: #ddd; }

That’s because our best and worst match classes are not on our custom elements. What we want is to be able to select the profiles’s parent elements from within the shadow DOM. :host-context pokes past the custom element to match the, er, match classes we want to style.

:host-context(.bestmatch) { background-color: #eef; } :host-context(.worstmatch) { background-color: #ddd; } CodePen Embed Fallback

Well, thanks for hanging out despite all the bad breath. (I know you couldn’t tell, but above when I was talking about your breath, I was secretly talking about my breath.)

How would you use ::part, ::slotted, :defined, :host, and :host-context in your web component? Let me know in the comments. (Or if you have cures to chronic halitosis, my wife would be very interested in to hear more.)

Web Component Pseudo-Classes and Pseudo-Elements are Easier Than You Think originally published on CSS-Tricks. You should get the newsletter.

From Users To Players: The Future Of UX Design In The Metaverse

Usability Geek - Sun, 02/27/2022 - 3:57pm
With over 4.8 billion users currently on the internet across the globe, the digital world as we once knew it continues to adapt to accommodate modern forms of interaction and communication across the...
Categories: Web Standards

Trailing Slashes on URLs: Contentious or Settled?

Css Tricks - Fri, 02/25/2022 - 11:13am

A fun deep dive from Zach. Do you have an opinion on which you should use?

1) https://website.com/foo/ 2) https://websites.com/foo

The first option has a “trailing slash.” The second does not.

I’ve always preferred this thinking: you use a trailing slash if that page has child pages (as in, it is something of a directory page, even if has unique content of its own). If it’s the end-of-the-line (of content), no trailing slash.

I say that, but this very site doesn’t practice it. Blog posts on this site are like css-tricks.com/blog-post/ with a trailing slash and if you leave off the trailing slash, WordPress will redirect to include it. That’s part of the reason Zach is interested here. Redirects come with a performance penalty, so it’s ideal to have it happen as infrequently possible.

Performance is one thing, but SEO is another one. If you render the same content, both with and without a trailing slash, that’s theoretically a duplicate content penalty and a no-no. (Although that seems weird to me, I would think Google would smart enough not to be terribly concerned by this.)

Where resources resolve to seems like the biggest deal to me. Here’s Zach:

If you’re using relative resource URLs, the assets may be missing on Vercel, Render, and Azure Static Web Apps (depending on which duplicated endpoint you’ve visited).

<img src="image.avif"> on /resource/ resolves to /resource/image.avif

<img src="image.avif"> on /resource resolves to /image.avif

That’s a non-trivial difference and, to me, a reason the redirect is worth it. Can’t be having a page with broken resources for something this silly.

What complicates this is that the site-building framework might have opinions about this and a hosting provider might have opinions about this. As Zach notes, there are some disagreements among hosts, so it’s something to watch for.

Me, I’d go with the grain as much as I possibly could. As long as redirects are in place and I don’t have to override any config, I’m cool.

To Shared LinkPermalink on CSS-Tricks

Trailing Slashes on URLs: Contentious or Settled? originally published on CSS-Tricks. You should get the newsletter.

When to Avoid the text-decoration Shorthand Property

Css Tricks - Fri, 02/25/2022 - 5:28am

In my recent article about CSS underline bugs in Chrome, I discussed text-decoration-thickness and text-underline-offset, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.

Let me demonstrate the usefulness of text-decoration-thickness on a simple example. The Ubuntu web font has a fairly thick default underline. We can make this underline thinner like so:

:any-link { text-decoration-thickness: 0.08em; }

/explanation Throughout this article, I will use the :any-link selector instead of the a element to match hyperlinks. The problem with the a tag as a selector is that it matches all <a> elements, even the ones that don’t have a href attribute and thus aren’t hyperlinks. The :any-link selector only matches <a> elements that are hyperlinks. Web browsers also use :any-link instead of a in their user agent stylesheets.

Hover underlines

Many websites, including Google Search and Wikipedia, remove underlines from links and only show them when the user hovers a link. Removing underlines from links in body text is not a good idea, but it can make sense in places where links are more spaced apart (navigation, footer, etc.). With that being said, here’s a simple implementation of hover underlines for links in the website’s header:

header :any-link { text-decoration: none; } header :any-link:hover { text-decoration: underline; }

But there’s a problem. If we tested this code in a browser, we’d notice that the underlines in the header have the default thickness, not the thinner style that we declared earlier. Why did text-decoration-thickness stop working after we added hover underlines?

CodePen Embed Fallback

Let’s look at the full CSS code again. Can you think of a reason why the custom thickness doesn’t apply to the hover underline?

:any-link { text-decoration-thickness: 0.08em; } header :any-link { text-decoration: none; } header :any-link:hover { text-decoration: underline; }

The reason for this behavior is that text-decoration is a shorthand property and text-decoration-thickness its associated longhand property. Setting text-decoration to none or underline has the side effect of re-initializing the other three text decoration components (thickness, style, and color). This is defined in the CSS Text Decoration module:

The text-decoration property is a shorthand for setting text-decoration-line, text-decoration-thickness, text-decoration-style, and text-decoration-color in one declaration. Omitted values are set to their initial values.

You can confirm this in the browser’s DevTools by selecting one of the hyperlinks in the DOM inspector and then expanding the text-decoration property in the CSS pane.

In order to get text-decoration-thickness to work on hover underlines, we’ll have to make a small change to the above CSS code. There are actually multiple ways to achieve this. We could:

  • set text-decoration-thickness after text-decoration,
  • declare the thickness in the text-decoration shorthand, or
  • use text-decoration-line instead of text-decoration.
Choosing the best text-decoration option

Our first thought might be to simply repeat the text-decoration-thickness declaration in the :hover state. It’s a quick and simple fix that indeed works.

/* OPTION A */ header :any-link { text-decoration: none; } header :any-link:hover { text-decoration: underline; text-decoration-thickness: 0.08em; /* set thickness again */ }

However, since text-decoration is a shorthand and text-decoration-thickness is its associated longhand, there really should be no need to use both at the same time. As a shorthand, text-decoration allows setting both the underline itself and the underline’s thickness, all in one declaration.

/* OPTION B */ header :any-link { text-decoration: none; } header :any-link:hover { text-decoration: underline 0.08em; /* set both line and thickness */ }

If this code looks unfamiliar to you, that could be because the idea of using text-decoration as a shorthand is relatively new. This property was only subsequently turned into a shorthand in the CSS Text Decoration module. In the days of CSS 2, text-decoration was a simple property.

Unfortunately, Safari still hasn’t fully caught up with these changes. In the WebKit browser engine, the shorthand variant of text-decoration remains prefixed (-webkit-text-decoration), and it doesn’t support thickness values yet. See WebKit bug 230083 for more information.

This rules out the text-decoration shorthand syntax. The above code won’t work in Safari, even if we added the -webkit- prefix. Luckily, there’s another way to avoid repeating the text-decoration-thickness declaration.

When text-decoration was turned into a shorthand, a new text-decoration-line longhand was introduced to take over its old job. We can use this property to hide and show the underline without affecting the other three text decoration components.

/* OPTION C */ header :any-link { text-decoration-line: none; } header :any-link:hover { text-decoration-line: underline; }

Since we’re only updating the line component of the text-decoration value, the previously declared thickness remains intact. I think that this is the best way to implement hover underlines.

Be aware of shorthands

Keep in mind that when you set a shorthand property, e.g., text-decoration: underline, any missing parts in the value are re-initialized. This is also why styles such as background-repeat: no-repeat are undone if you set background: url(flower.jpg) afterwards. See the article “Accidental CSS Resets” for more examples of this behavior.

When to Avoid the text-decoration Shorthand Property originally published on CSS-Tricks. You should get the newsletter.

Manuel Matuzovic’s CSS Specificity Demo

Css Tricks - Fri, 02/25/2022 - 5:27am

If you’re looking for a primer on CSS specificity, we’ve got that. And if you’re trying to get ahead of the game, you should be aware of CSS Cascade Layers as well.

One of the ways to help get a grasp of CSS specificity is thinking terms of “what beats what” or how strong the specificity is. Manuel Matuzovic has a helpful interactive step-by-step demo. You keep clicking the “Add selector” button, and the CSS shown (and applied to the page) changes with ever-increasingly-strong selectors applied to the body that change the background-color. At the end, it veers into not-really-selectors trickery, like using @keyframes to override things.

More specificity practice

If you enjoyed the trickery at the end, check out Francisco Dias’ A Specificity Battle!, an article we published a few years back that does a back-and-forth styling battle with nineteen steps “selecting” the same element to re-style it. CSS is cray sometimes.

To Shared LinkPermalink on CSS-Tricks

Manuel Matuzovic’s CSS Specificity Demo originally published on CSS-Tricks. You should get the newsletter.

My white whale: A use case for will-change

Css Tricks - Thu, 02/24/2022 - 10:59am

 Nic Chan:

[…] the will-change property landed in major browsers in August 2015, and I’ve been on the lookout for when to use it ever since. It might seem self-evident to apply it to commonly animated properties such as transform or opacity, but the browser already classifies them as composite properties, thus, they are known as the few properties that you can already expect decent animation performance from. So, heeding the advice of the great developers who came before me, I was cautious and waited for the right opportunity to come along.

I was thinking-out-loud about this as well on ShopTalk not too long ago. I get the spirit behind will-change. It’s like responsive images or DNS prefetching: you give the browser extra information about what you’re about to do, and it can optimize it when it happens. But with will-change… when? Why isn’t there a simple reduced test case demo to showcase something with bad performance, then will-change being applied, and it becomes good performance?

Well Nic found one little directly useful case where a hover-transformed pseudo-element leaves a little dingus of color behind in Safari, and that goes away if you use will-change. I tested it in the latest versions of Safari and found it to be true. Alrighty then, one use case!

I’d love to see a more obvious direct use case. I imagine the sweet spot is on lower-power devices (that still have GPUs) but are new enough to know what will-change is.

To Shared LinkPermalink on CSS-Tricks

My white whale: A use case for will-change originally published on CSS-Tricks. You should get the newsletter.

Explain the First 10 Lines of Twitter’s Source Code to Me

Css Tricks - Thu, 02/24/2022 - 5:31am

For the past few weeks, I’ve been hiring for a senior full-stack JavaScript engineer at my rental furniture company, Pabio. Since we’re a remote team, we conduct our interviews on Zoom, and I’ve observed that some developers are not great at live-coding or whiteboard interviews, even if they’re good at the job. So, instead, we have an hour-long technical discussion where I ask them questions about web vitals, accessibility, the browser wars, and other similar topics about the web. One of the questions I always like to ask is: “Explain the first ten or so lines of the Twitter source code to me.”

I think it’s a simple test that tells me a lot about the depth of fundamental front-end knowledge they have, and this article lists the best answers.

For context, I share my screen, open Twitter.com and click View source. Then I ask them to go line-by-line to help me understand the HTML, and they can say as much or as little as they like. I also zoom in to make the text more legible, so you don’t see the full line but you get an idea. Here’s what it looks like:

Note that since our technical discussion is a conversation. I don’t expect a perfect answer from anyone. If I hear some right keywords, I know that the candidate knows the concept, and I try to push them in the right direction.

Line 1: <!DOCTYPE html>

The first line of every document’s source code is perfect for this interview because how much a candidate knows about the DOCTYPE declaration closely resembles how many years of experience they have. I still remember my Dreamweaver days with the long XHTML DOCTYPE line, like Chris listed in his article “The Common DOCTYPES” from 2009.

Perfect answer: This is the document type (doc-type) declaration that we always put as the first line in HTML files. You might think that this information is redundant because the browser already knows that the MIME type of the response is text/html; but in the Netscape/Internet Explorer days, browsers had the difficult task of figuring out which HTML standard to use to render the page from multiple competing versions.

This was especially annoying because each standard generated a different layout so this tag was adopted to make it easy for browsers. Previously, DOCTYPE tags were long and even included the specification link (kinda like SVGs have today), but luckily the simple <!doctype html> was standardized in HTML5 and still lives on.

Also accepted: This is the DOCTYPE tag to let the browser know that this is an HTML5 page and should be rendered as such.

Line 2: <html dir="ltr" lang="en">

This line in the source code tells me if the candidate knows about accessibility and localization. Surprisingly, only a few people knew about the dir attribute in my interviews, but it’s a great segue into a discussion about screen readers. Almost everyone was able to figure out the lang="en" attribute, even if they hadn’t used it before.

Perfect answer: This is the root element of an HTML document and all other elements are inside this one. Here, it has two attributes, direction and language. The direction attribute has the value left-to-right to tell user agents which direction the content is in; other values are right-to-left for languages like Arabic, or just auto which leaves it to the browser to figure out.

The language attribute tells us that all content inside this tag is in English; you can set this value to any language tag, even to differentiate en-us and en-gb, for example. This is also useful for screen readers to know which language to announce in.

Line 3: <meta charset="utf-8">

Perfect answer: The meta tag in the source code is for supplying metadata about this document. The character set (char-set) attribute tells the browser which character encoding to use, and Twitter uses the standard UTF-8 encoding. UTF-8 is great because it has many character points so you can use all sorts of symbols and emoji in your source code. It’s important to put this tag near the beginning of your code so the browser hasn’t already started parsing too much text when it comes across this line; I think the rule is to put it in the first kilobyte of the document, but I’d say the best practice is to put it right at the top of <head>.

As a side note, it looks like Twitter omits the <head> tag for performance reasons (less code to load), but I still like to make it explicit as it’s a clear home for all metadata, styles, etc.

Line 4: <meta name="viewport" content="width=device-...

Perfect answer: This meta tag in the source code is for properly sizing the webpage on small screens, like smartphones. If you remember the original iPhone keynote, Steve Jobs showed the entire New York Times website on that tiny 4.5-inch screen; back then it was an amazing feature that you had to pinch to zoom to actually be able to read.

Now that websites are responsive by design, width=device-width tells the browser to use 100% of the device’s width as the viewport so there’s no horizontal scrolling, but you can even specify specific pixel values for width. The standard best practice is to set the initial scale to 1 and the width to device-width so people can still zoom around if they wish.

The screenshot of the source code doesn’t show these values but it’s good to know: Twitter also applies user-scalable=0 which, as the name suggests, disables the ability to zoom. This is not good for accessibility but makes the webpage feel more like a native app. It also sets maximum-scale=1 for the same reason (you can use minimum and maximum scale to clamp the zoom-ablity between these values). In general, setting the full width and initial scale is enough.

Line 5: <meta property="og:site_name" content="Twitt...

About 50% of all candidates knew about Open Graph tags, and a good answer to this question shows that they know about SEO.

Perfect answer: This tag is an Open Graph (OG) meta tag for the site name, Twitter. The Open Graph protocol was made by Facebook to make it easier to unfurl links and show their previews in a nice card layout; developers can add all sorts of authorship details and cover images for fancy sharing. In fact, these days it’s even common to auto-generate the open graph image using something like Puppeteer. (CSS-Tricks uses a WordPress plugin that does it.)

Another interesting side note is that meta tags usually have the name attribute, but OG uses the non-standard property attribute. I guess that’s just Facebook being Facebook? The title, URL, and description Open Graph tags are kinda redundant because we already have regular meta tags for these, but people add them just to be safe. Most sites these days use a combination of Open Graph and other metatags and the content on a page to generate rich previews.

Line 6: <meta name="apple-mobile-web-app-title" cont...

Most candidates didn’t know about this one, but experienced developers can talk about how to optimize a website for Apple devices, like apple-touch-icons and Safari pinned tab SVGs.

Perfect answer: You can pin a website on an iPhone’s homescreen to make it feel like a native app. Safari doesn’t support progressive web apps and you can’t really use other browser engines on iOS, so you don’t really have other options if you want that native-like experience, which Twitter, of course, likes. So they add this to tell Safari that the title of this app is Twitter. The next line is similar and controls how the status bar should look like when the app has launched.

Line 8: <meta name="theme-color" content="#ffffff"...

Perfect answer: This is the proper web standards-esque equivalent of the Apple status bar color meta tag. It tells the browser to theme the surrounding UI. Chrome on Android and Brave on desktop both do a pretty good job with that. You can put any CSS color in the content, and can even use the media attribute to only show this color for a specific media query like, for example, to support a dark theme. You can also define this and additional properties in the web app manifest.

Line 9: <meta http-equiv="origin-trial" content="...

Nobody I interviewed knew about this one. I would assume that you’d know this only if you have in-depth knowledge about all the new things that are happening on the standards track.

Perfect answer: Origin trials let us use new and experimental features on our site and the feedback is tracked by the user agent and reported to the web standards community without users having to opt-in to a feature flag. For example, Edge has an origin trial for dual-screen and foldable device primitives, which is pretty cool as you can make interesting layouts based on whether a foldable phone is opened or closed.

Also accepted: I don’t know about this one.

Line 10: html{-ms-text-size-adjust:100%;-webkit-text...

Almost nobody knew about this one too; only if you know about CSS edge cases and optimizations, you’d be able to figure this line out.

Perfect answer: Imagine that you don’t have a mobile responsive site and you open it on a small screen, so the browser might resize the text to make it bigger so it’s easier to read. The CSS text-size-adjust property can either disable this feature with the value none or specify a percentage up to which the browser is allowed to make the text bigger.

In this case, Twitter says the maximum is 100%, so the text should be no bigger than the actual size; they just do that because their site is already responsive and they don’t want to risk a browser breaking the layout with a larger font size. This is applied to the root HTML tag so it applies to everything inside it. Since this is an experimental CSS property, vendor prefixes are required. Also, there’s a missing <style> before this CSS, but I’m guessing that’s minified in the previous line and we don’t see it.

Also accepted: I don’t know about this property in specific but the -ms and -webkit- are vendor prefixes needed by Internet Explorer and WebKit-based browsers, respectively, for non-standard properties. We used to require these prefixes when CSS3 came out, but as properties go from experimental to stable or are adopted to a standards track, these prefixes go away in favor of a standardized property.

Bonus — Line 11: body{margin:0;}

This line from Twitter’s source code is particularly fun because you can follow-up with a question about the difference between resetting and normalizing a webpage. Almost everyone knew a version of the right answer.

Perfect answer: Because different browsers have different default styles (user agent stylesheet), you want to overwrite them by resetting properties so your site looks the same across devices. In this case, Twitter is telling the browser to remove the body tag’s default margin. This is just to reduce browser inconsistencies, but I prefer normalizing the styles instead of resetting them, i.e., applying the same defaults across browsers rather than removing them altogether. People even used to use * { margin: 0 } which is totally overkill and not great for performance, but now it’s common to import something like normalize.css or reset.css (or even something newer) and start from there.

More lines!

I always enjoy playing with the browser Inspector tool to see how sites are made, which is how I came up with this idea. Even though I consider myself sort of an expert on semantic HTML, I learn something new every time I do this exercise.

Since Twitter is mostly a client-side React app, there’s only a few dozen lines in the source code. Even with that, there’s so much to learn! There are a few more interesting lines in the Twitter source code that I leave as an exercise for you, the reader. How many of them could you explain in an interview?

<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Twitter">

…tells browsers that users can add Twitter as a search engine.

<link rel="preload" as="script" crossorigin="anonymous" href="https://abs.twimg.com/responsive-web/client-web/polyfills.cad508b5.js" nonce="MGUyZTIyN2ItMDM1ZC00MzE5LWE2YmMtYTU5NTg2MDU0OTM1" />

…has many interesting attributes that can be discussed, especially nonce.

<link rel="alternate" hreflang="x-default" href="https://twitter.com/" />

…for international landing pages.

:focus:not([data-focusvisible-polyfill]){outline: none;}

…for removing the focus outline when not using keyboard navigation (the CSS :focus-visible selector is polyfilled here).

Explain the First 10 Lines of Twitter’s Source Code to Me originally published on CSS-Tricks. You should get the newsletter.

CSS Database Queries? Sure We Can!

Css Tricks - Wed, 02/23/2022 - 10:04am

Kinda silly sounding, isn’t it? CSS database queries. But, hey, CSS is capable of talking to other languages in the sense that it can set the values of things that they can read. Plus, CSS can request other files, and I suppose a server could respond to that request with something it requested from a database.

But I’m getting ahead of myself. The idea of CSS database queries was a joke tweet going around the other day about recruiters looking for a developer who can connect to a database with CSS. Lee Meichin wrote “Yes, I can connect to a DB in CSS” as an equally funny retort.

What’s the trick behind CSS database queries?

It’s nicely elaborate:

  1. Use a hand-modified-to-ESM version of SQL.js, which is SQLite in JavaScript.
  2. Get a database ready that SQL.js can query.
  3. Build a Houdini PaintWorklet that executes queries in JavaScript and paints the results back to the screen in that <canvas>-y way that PaintWorklets do.
  4. Pass the query you want to run into the worklet by way of a CSS custom property.

So, the usage is like this in the end:

<script> CSS.paintWorklet.addModule('./cssdb.js') </script> <style> main { --sql-query: SELECT name FROM test; background: paint(sql-db); } </style>

Which, you gotta admit, is connecting and querying a database in CSS.

This reminds me that Simon Willison did this last year with a totally different approach. His concept was that you have RESTful endpoints, like /api/roadside_attractions, that return JSON data. But then as an alternative endpoint, you could make that /api/roadside_attractions.css which would return a valid CSS file with all the data as CSS custom properties.

So, instead it looks like this:

<link rel="stylesheet" href="/api/roadside_attractions.css"> <style> .attraction-name:after { content: var(--name); } .attraction-address:after { content: var(--address); } </style> <p class="attraction-name">Attraction name: </p> <p class="attraction-address">Address: </p>

Which, again, is essentially connecting to a database in CSS (with HTML required, though). You can literally see it work.

CSS Database Queries? Sure We Can! originally published on CSS-Tricks. You should get the newsletter.

5 Accessibility Quick Wins You Can Implement Today

Css Tricks - Wed, 02/23/2022 - 5:15am

Let’s face it: building an AA or AAA-accessible product can be quite daunting. Luckily, having an accessible product isn’t all-or-nothing. Even seemingly small improvements can have nice quality of life benefits for many people.

In that spirit, here are five accessibility quick wins you can implement today.

Quick Win 1: Indicate the Current Page

It’s probably safe to assume that a different style is the most common way to communicate the current page of a site or app. However, even if those styles are clear and with great contrast ratios, they’re still only a visual cue.

So what happens if a person with limited vision cannot see that separation? How will they know what page they’re on?

Creating an accessible product is to ensure its markup communicates as clearly as its design.

Adding aria-current="page" to the active navigation element is one way to ensure markup and design communicate the same information with or without assistive technologies.

<a aria-current="page" href="/">Home</a> &#x1f389; Bonus

Use CSS attribute selectors to style the aria-current="page" element to keep the visual and markup cues linked.

[aria-current="page"] { /* Active element styles */ } Quick Win 2: Document Language

While some people can visit a website and determine the language or locale of its content, not all people have that luxury. Again, markup must communicate the same information as the visual design — even if that information may seem implied.

Add the lang attribute to the <html> tag to communicate not only the document’s language, but its locale. This will help assistive technologies like screen readers understand and communicate the content. Even if the app only supports one language, this can be a nice quality of life improvement for many people.

<html lang="en-US">

For apps which support multiple languages, the <html> element is likely not the only one to need its lang value defined. Use the lang attribute on specific elements whose language differs from the rest of the document, like links within a language toggle menu. In this case, pair the use of lang with the hreflang attribute to not only communicate the language of the link itself, but also of its destination.

<a lang="fi" hreflang="fi" href="/" title="Suomeksi"> <bdi>Suomeksi</bdi> </a> Quick Win 3: Use prefers-reduced-motion

Whether drawing attention to actions or updates, or creating a sense of life and charm, adding motion to an app can really elevate its experience. However, some people may find that experience disorienting.

Windows and MacOS both offer a setting at the OS level for people to greatly reduce the amount of motion when using their systems. The prefers-reduced-motion setting can greatly improve the experience on a computer, but it does not extends beyond the UI of the operating system. So wouldn’t it be nice if our apps could respect that same system setting and provide a more static experience for those who prefer it?

Well, with CSS media queries, they can.

The prefers-reduced-motion media query can be used to greatly reduce or remove all motion from an app whenever the system setting is enabled.

CodePen Embed Fallback @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }

The blanket approach shown here prevents all motion, but it can leave little room for nuance. It’d be best to review the needs of those using the product, but consider these other options as well.

One approach could be to only animate one property at a time in prefers-reduced-motion settings. So consider a <Modal /> that fades and scales into view with opacity and transform. In reduced motion environments, only the opacity would transition. The scaling effect would be removed as they are more commonly problematic than fading.

Another option could be to look at the prefers-reduced-motion environment a bit more literally and remove all motion. This would do away with our scaling modals, sliding drawers, and bouncing notifications, but would still leave room for color transitions on links and buttons.

Quick Win 4: Indicate Data Sorting State

A common theme across all of these tips is to ensure that an app’s visual design and markup communicate the same things. So, when the design uses an arrow to indicate the sort direction of a table column, how can that also be communicated in the markup?

Setting the aria-sort attribute to ascending /descending on the header of the actively-sorted column allows the markup to communicate the same content structure as a visual indicator in the UI.

This will help ensure that people using assistive technologies and those who aren’t can understand the content in the same way.

<thead> <tr> <th>First Name</th> <th aria-sort="ascending">Last Name</th> </tr> </thead> Quick Win 5: Lazy Loading Lists

Whether scrolling through an endless stream of tweets or through an impossible-to-decide list of products, the web has fully embraced lazy loading long lists of data (and alliteration, apparently).

This is when the aria-setsize and aria-posinset attributes become very valuable. While a person’s progression through the list can be communicated visually in many different ways, these attributes are used to communicate that same progression to many assistive technologies.

As developers, we likely have access to the length of an entire list as well as the index of the current items being displayed. With that, the aria-setsize attribute would define the total length of the list, while the aria-posinset attribute would define an item’s specific position (or index) within that list.

If the total length of the list is not known, then aria-setsize should be set to -1.

With these attributes, assistive technologies can better interpret a list and a person can better understand their position within it.

<h2 id="top-artists-title">Top Artists of 2021</h2> <ul role="listbox" aria-labelledby="top-artists-title"> <li role="option" aria-setsize="20" aria-posinset="5">Bloodbound</li> <li role="option" aria-setsize="20" aria-posinset="6">Manimal</li> <li role="option" aria-setsize="20" aria-posinset="7">Powerwolf</li> </ul>

Take a listen to how these attributes are announced using MacOS VoiceOver.

&#x1f389; Bonus Win: Axe-DevTools Extension

Implementing those five accessibility quick wins is a great start, but that’s exactly what it is —a start. There’s a sprawling landscape of assistive technologies and sets of abilities a person can posses, and navigating it all alone can feel overwhelming.

Fortunately, there are plenty of tools to help with auditing a product’s accessibility that make the journey much more manageable. My personal favorite — my trusty accessibility compass — is the Axe-DevTools browser extension.

Running the Axe-DevTools accessibility scanner can return tons of valuable information. Not only will it display all issues and warnings found on the page, but it groups them by approximate severity. It can also highlight the element on the page or in the Elements tab and provide links to learn more about the specific issue.

However, most importantly, it will offer clear and concise approaches to fix the specific issue.

Wrapping Up

A product isn’t made accessible overnight; nor is a product’s accessibility work ever really complete. Like anything else on the web, accessibility evolves and requires maintenance. However, even seemingly small additions can have an impact on a product’s accessibility and a person’s overall experience.

After stepping into a new codebase, these are often some of the first few things I look into — some “low-hanging fruit” of accessibility, if you will.

Reaching AAA or even AA conformance can feel like scaling an 8,000 meter peak. These steps won’t carry you to the summit, but an expedition is never completed in a single stride.

Resources

5 Accessibility Quick Wins You Can Implement Today originally published on CSS-Tricks. You should get the newsletter.

Reliably Send an HTTP Request as a User Leaves a Page

Css Tricks - Tue, 02/22/2022 - 5:24am

On several occasions, I’ve needed to send off an HTTP request with some data to log when a user does something like navigate to a different page or submit a form. Consider this contrived example of sending some information to an external service when a link is clicked:

<a href="/some-other-page" id="link">Go to Page</a> <script> document.getElementById('link').addEventListener('click', (e) => { fetch("/log", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ some: "data" }) }); }); </script>

There’s nothing terribly complicated going on here. The link is permitted to behave as it normally would (I’m not using e.preventDefault()), but before that behavior occurs, a POST request is triggered on click. There’s no need to wait for any sort of response. I just want it to be sent to whatever service I’m hitting.

On first glance, you might expect the dispatch of that request to be synchronous, after which we’d continue navigating away from the page while some other server successfully handles that request. But as it turns out, that’s not what always happens.

Browsers don’t guarantee to preserve open HTTP requests

When something occurs to terminate a page in the browser, there’s no guarantee that an in-process HTTP request will be successful (see more about the “terminated” and other states of a page’s lifecycle). The reliability of those requests may depend on several things — network connection, application performance, and even the configuration of the external service itself.

As a result, sending data at those moments can be anything but reliable, which presents a potentially significant problem if you’re relying on those logs to make data-sensitive business decisions.

To help illustrate this unreliability, I set up a small Express application with a page using the code included above. When the link is clicked, the browser navigates to /other, but before that happens, a POST request is fired off.

While everything happens, I have the browser’s Network tab open, and I’m using a “Slow 3G” connection speed. Once the page loads and I’ve cleared the log out, things look pretty quiet:

But as soon as the link is clicked, things go awry. When navigation occurs, the request is cancelled.

And that leaves us with little confidence that the external service was actually able process the request. Just to verify this behavior, it also occurs when we navigate programmatically with window.location:

document.getElementById('link').addEventListener('click', (e) => { + e.preventDefault(); // Request is queued, but cancelled as soon as navigation occurs. fetch("/log", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ some: 'data' }), }); + window.location = e.target.href; });

Regardless of how or when navigation occurs and the active page is terminated, those unfinished requests are at risk for being abandoned.

But why are they cancelled?

The root of the issue is that, by default, XHR requests (via fetch or XMLHttpRequest) are asynchronous and non-blocking. As soon as the request is queued, the actual work of the request is handed off to a browser-level API behind the scenes.

As it relates to performance, this is good — you don’t want requests hogging the main thread. But it also means there’s a risk of them being deserted when a page enters into that “terminated” state, leaving no guarantee that any of that behind-the-scenes work reaches completion. Here’s how Google summarizes that specific lifecycle state:

A page is in the terminated state once it has started being unloaded and cleared from memory by the browser. No new tasks can start in this state, and in-progress tasks may be killed if they run too long.

In short, the browser is designed with the assumption that when a page is dismissed, there’s no need to continue to process any background processes queued by it.

So, what are our options?

Perhaps the most obvious approach to avoid this problem is, as much as possible, to delay the user action until the request returns a response. In the past, this has been done the wrong way by use of the synchronous flag supported within XMLHttpRequest. But using it completely blocks the main thread, causing a host of performance issues — I’ve written about some of this in the past — so the idea shouldn’t even be entertained. In fact, it’s on its way out of the platform (Chrome v80+ has already removed it).

Instead, if you’re going to take this type of approach, it’s better to wait for a Promise to resolve as a response is returned. After it’s back, you can safely perform the behavior. Using our snippet from earlier, that might look something like this:

document.getElementById('link').addEventListener('click', async (e) => { e.preventDefault(); // Wait for response to come back... await fetch("/log", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ some: 'data' }), }); // ...and THEN navigate away. window.location = e.target.href; });

That gets the job done, but there are some non-trivial drawbacks.

First, it compromises the user’s experience by delaying the desired behavior from occurring. Collecting analytics data certainly benefits the business (and hopefully future users), but it’s less than ideal to make your present users to pay the cost to realize those benefits. Not to mention, as an external dependency, any latency or other performance issues within the service itself will be surfaced to the user. If timeouts from your analytics service cause a customer from completing a high-value action, everyone loses.

Second, this approach isn’t as reliable as it initially sounds, since some termination behaviors can’t be programmatically delayed. For example, e.preventDefault() is useless in delaying someone from closing a browser tab. So, at best, it’ll cover collecting data for some user actions, but not enough to be able to trust it comprehensively.

Instructing the browser to preserve outstanding requests

Thankfully, there are options to preserve outstanding HTTP requests that are built into the vast majority of browsers, and that don’t require user experience to be compromised.

Using Fetch’s keepalive flag

If the keepalive flag is set to true when using fetch(), the corresponding request will remain open, even if the page that initiated that request is terminated. Using our initial example, that’d make for an implementation that looks like this:

<a href="/some-other-page" id="link">Go to Page</a> <script> document.getElementById('link').addEventListener('click', (e) => { fetch("/log", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ some: "data" }), keepalive: true }); }); </script>

When that link is clicked and page navigation occurs, no request cancellation occurs:

Instead, we’re left with an (unknown) status, simply because the active page never waited around to receive any sort of response.

A one-liner like this an easy fix, especially when it’s part of a commonly used browser API. But if you’re looking for a more focused option with a simpler interface, there’s another way with virtually the same browser support.

Using Navigator.sendBeacon()

The Navigator.sendBeacon()function is specifically intended for sending one-way requests (beacons). A basic implementation looks like this, sending a POST with stringified JSON and a “text/plain” Content-Type:

navigator.sendBeacon('/log', JSON.stringify({ some: "data" }));

But this API doesn’t permit you to send custom headers. So, in order for us to send our data as “application/json”, we’ll need to make a small tweak and use a Blob:

<a href="/some-other-page" id="link">Go to Page</a> <script> document.getElementById('link').addEventListener('click', (e) => { const blob = new Blob([JSON.stringify({ some: "data" })], { type: 'application/json; charset=UTF-8' }); navigator.sendBeacon('/log', blob)); }); </script>

In the end, we get the same result — a request that’s allowed to complete even after page navigation. But there’s something more going on that may give it an edge over fetch(): beacons are sent with a low priority.

To demonstrate, here’s what’s shown in the Network tab when both fetch() with keepalive and sendBeacon() are used at the same time:

By default, fetch() gets a “High” priority, while the beacon (noted as the “ping” type above) have the “Lowest” priority. For requests that aren’t critical to the functionality of the page, this is a good thing. Taken straight from the Beacon specification:

This specification defines an interface that […] minimizes resource contention with other time-critical operations, while ensuring that such requests are still processed and delivered to destination.

Put another way, sendBeacon() ensures its requests stay out of the way of those that really matter for your application and your user’s experience.

An honorable mention for the ping attribute

It’s worth mentioning that a growing number of browsers support the ping attribute. When attached to links, it’ll fire off a small POST request:

<a href="http://localhost:3000/other" ping="http://localhost:3000/log"> Go to Other Page </a>

And those requests headers will contain the page on which the link was clicked (ping-from), as well as the href value of that link (ping-to):

headers: { 'ping-from': 'http://localhost:3000/', 'ping-to': 'http://localhost:3000/other' 'content-type': 'text/ping' // ...other headers },

It’s technically similar to sending a beacon, but has a few notable limitations:

  1. It’s strictly limited for use on links, which makes it a non-starter if you need to track data associated with other interactions, like button clicks or form submissions.
  2. Browser support is good, but not great. At the time of this writing, Firefox specifically doesn’t have it enabled by default.
  3. You’re unable to send any custom data along with the request. As mentioned, the most you’ll get is a couple of ping-* headers, along with whatever other headers are along for the ride.

All things considered, ping is a good tool if you’re fine with sending simple requests and don’t want to write any custom JavaScript. But if you’re needing to send anything of more substance, it might not be the best thing to reach for.

So, which one should I reach for?

There are definitely tradeoffs to using either fetch with keepalive or sendBeacon() to send your last-second requests. To help discern which is the most appropriate for different circumstances, here are some things to consider:

You might go with fetch() + keepalive if:
  • You need to easily pass custom headers with the request.
  • You want to make a GET request to a service, rather than a POST.
  • You’re supporting older browsers (like IE) and already have a fetch polyfill being loaded.
But sendBeacon() might be a better choice if:
  • You’re making simple service requests that don’t need much customization.
  • You prefer the cleaner, more elegant API.
  • You want to guarantee that your requests don’t compete with other high-priority requests being sent in the application.
Avoid repeating my mistakes

There’s a reason I chose to do a deep dive into the nature of how browsers handle in-process requests as a page is terminated. A while back, my team saw a sudden change in the frequency of a particular type of analytics log after we began firing the request just as a form was being submitted. The change was abrupt and significant — a ~30% drop from what we had been seeing historically.

Digging into the reasons this problem arose, as well as the tools that are available to avoid it again, saved the day. So, if anything, I’m hoping that understanding the nuances of these challenges help someone avoid some of the pain we ran into. Happy logging!

Reliably Send an HTTP Request as a User Leaves a Page originally published on CSS-Tricks. You should get the newsletter.

Syndicate content
©2003 - Present Akamai Design & Development.