CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 04 Apr 2022 18:25:25 +0000 en-US hourly 1 https://wordpress.org/?v=5.9.2 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 CSS-Tricks https://css-tricks.com 32 32 45537868 Adding CDN Caching to a Vite Build https://css-tricks.com/adding-cdn-caching-to-a-vite-build/ https://css-tricks.com/adding-cdn-caching-to-a-vite-build/#respond Mon, 04 Apr 2022 18:25:23 +0000 https://css-tricks.com/?p=364166 Content delivery networks, or CDNs, allow you to improve the delivery of your website’s static resources, most notably, with CDN caching. They do this by serving your content from edge locations, which are located all over the world. When a …


Adding CDN Caching to a Vite Build originally published on CSS-Tricks. You should get the newsletter.

]]>
Content delivery networks, or CDNs, allow you to improve the delivery of your website’s static resources, most notably, with CDN caching. They do this by serving your content from edge locations, which are located all over the world. When a user browses to your site, and your site requests resources from the CDN, the CDN will route that request to the nearest edge location. If that location has the requested resources, either from that user’s prior visit, or from another person, then the content will be served from cache. If not, the CDN will request the content from your underlying domain, cache it, and serve it.

There are countless CDNs out there, but for this post we’ll be using AWS CloudFront. We’ll look at setting up a CloudFront distribution to serve all our site’s assets: JavaScript files, CSS files, font files, etc. Then we’ll see about integrating it into a Vite build. If you’d like to learn more about Vite, I have an introduction here.

Setting up a CloudFront CDN distribution

Let’s jump right in and set up our CloudFront CDN distribution.

For any serious project, you should be setting up your serverless infrastructure with code, using something like the Serverless Framework, or AWS’s CDK. But to keep things simple, here, we’ll set up our CDN using the AWS console.

Head on over to the CloudFront homepage. At the top right, you should see an orange button to create a new distribution.

CloudFront CDN Distributions screen.

The creation screen has a ton of options, but for the most part the default selections will be fine. First and foremost, add the domain where your resources are located.

CloudFront CDN distribution creation screen.

Next, scroll down and find the Response headers policy dropdown, and choose “CORS-With-Preflight.”

CloudFront response headers settings.

Lastly, click the Create Distribution button at the bottom, and hopefully you’ll see your new distribution.

CloudFront CDN distribution overview screen.

Integrating the CDN with Vite

It’s one thing for our CDN to be set up and ready to serve our files. But it’s another for our site to actually know how to request them from our CDN. I’ll walk through integrating with Vite, but other build systems, like webpack or Rollup, will be similar.

When Vite builds our site, it maintains a “graph” of all the JavaScript and CSS files that various parts of our site import, and it injects the appropriate <script> tags, <link> tags, or import() statements to load what’s needed. What we need to do is tell Vite to request these assets from our CDN when in production. Let’s see how.

Open up your vite.config.ts file. First, we’ll need to know if we’re on the live site (production) or in development (dev).

const isProduction = process.env.NODE_ENV === "production"; 

This works since Vite sets this environment variable when we run vite build, which is what we do for production, as opposed to dev mode with hot module reloading.

Next we tell Vite to draw our assets from our CDN like so, setting the base property of our config object:

export default defineConfig({
  base: isProduction ? process.env.REACT_CDN : "",

Be sure to set your REACT_CDN environment variable to your CDN’s location, which in this case, will be our CloudFront distribution’s location. Mine looks something (but not exactly) like this:

https://distributiondomainname.cloudfront.net

Watch your VitePWA settings!

As one final piece of cleanup, if you happen to be using the VitePWA plugin, be sure to reset your base property like this:

VitePWA({
  base: "/",

Otherwise, your web.manifest file will have invalid settings and cause errors.

Let’s see the CDN work

Once you’re all set up, browse to your site, and inspect any of the network requests for your script or CSS files. For starters, the protocol should be h2.

Showing the assets served via CDN caching in DevTools. Each file name includes a unique random string of letters and numbers.

From there, you can peek into the response headers of any one of those files, and you should see some CloudFront data in there:

Screenshot of a response header.

Cache busting

It’s hard to talk about CDNs without mentioning cache busting. CDNs like CloudFront have functionality to manually “eject” items from cache. But for Vite-built assets, we get this “for free” since Vite adds fingerprinting, or hash codes, to the filenames of the assets it produces.

So Vite might turn a home.js file into home-abc123.js during a build, but then if you change that file and rebuild, it might become home-xyz987.js. That’s good, as it will “break the cache,” and the newly built file will not be cached, so the CDN will have to turn to our host domain for the actual content.

CDN caching for other static assets

JavaScript, CSS, and font files aren’t the only kinds of assets that can benefit from CDN caching. If you have an S3 bucket you’re serving images out of, consider setting up a CloudFront distribution for it as well. There are options specifically for S3 which makes it a snap to create. Not only will you get the same edge caching, but HTTP/2 responses, which S3 does not provide.

Advanced CDN practices

Integrating a CDN here was reasonably straightforward, but we’re only enjoying a fraction of the potential benefits. Right now, users will browse to our app, our server will serve our root HTML file, and then the user’s browser will connect to our CDN to start pulling down all our static assets.

Going further, we would want to serve our entire site from a CDN. That way, it can communicate with our web server as needed for non-static and non-cached assets.

Conclusion

CDNs are a great way to improve the performance of your site. They provide edge caching and HTTP/2 out of the box. Not only that, but they’re reasonably easy to set up. Now you have a new tool in your belt to both set up a CDN and integrate it with Vite.


Adding CDN Caching to a Vite Build originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/adding-cdn-caching-to-a-vite-build/feed/ 0 364166
Tricks to Cut Corners Using CSS Mask and Clip-Path Properties https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/ https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/#respond Wed, 30 Mar 2022 16:35:08 +0000 https://css-tricks.com/?p=364279 We recently covered creating fancy borders with CSS mask properties, and now we are going to cut the corners with CSS mask and clip-path! A lot of techniques exist to cut different shapes from the corners of any element. …


Tricks to Cut Corners Using CSS Mask and Clip-Path Properties originally published on CSS-Tricks. You should get the newsletter.

]]>
We recently covered creating fancy borders with CSS mask properties, and now we are going to cut the corners with CSS mask and clip-path! A lot of techniques exist to cut different shapes from the corners of any element. In this article, we will consider modern techniques to create unique corner shapes while trying to work from reusable code that allows us to produce different results by adjusting variables.

Check this online tool to get an idea of what we are building. It’s a CSS generator where you select the shape, the corners, and the size then you get the code in no time!

We mainly have two types of cuts: a circular one and an angled one. For each, we can get the full shape or the border-only shape, not to mention that we can select the corners we want to cut. A lot of combinations!

Like in the previous article, we will make lots of use of the CSS mask property. So, if you are not familiar with it, I recommend reading the quick primer I wrote before continuing.

Circular cut-out

For a circular or rounded cut, we will use radial-gradient(). To cut four corners, the logical solution is to create four gradients, one for each corner:

Each gradient is taking a quarter of the element’s dimensions. The syntax of the gradient is self-explanatory:

radial-gradient(circle 30px at top left, #0000 98%, red) top left;

Translated, this renders a circle at the top-left corner with a 30px radius. The main color is transparent (#0000) and the remaining is red. The whole gradient is also placed so that it starts at the element’s top-left corner. Same logic for the three other gradients. The keyword circle can be omitted since we explicitly specified one value for the radius.

Like I did in the previous article, I will be using slightly bigger or smaller values this time around in order to avoid bad visual result. Here, I am using 98% instead of 100% to avoid jagged edges and 51% instead of 50% to create an overlap between gradients and avoid white spaces. This logic will follow throughout this article. In fact, you will find that adding or removing 1% or 1deg typically results in a nice visual.

We apply this to the CSS mask property and we are done!

We can actually optimize that code a little:

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%;
mask-size: 51% 51%;
mask-repeat: no-repeat;

This way, we use custom properties for the redundant values and, as a personal preference, I am using numeric values for the positions instead of keywords.

In the generator, I will use the following syntax:

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%/51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%/51% 51% no-repeat;

The shorthand syntax is easier to generate plus the whole value can be used as one custom property.

Can we use fewer gradients if we want?

Sure! One gradient can do the job. Hover the below to see the trick:

Here, we define one radial-gradient() with no size (by default it is 100% height and 100% width). This gives us a hole in the center. We translate/move the gradient by half the width and height of the image to move the hole to one corner. Since, by default, the CSS mask repeats, we get the same on each corner. We have four cut corners with only one gradient!

The only drawback of this method is that we need to know the width and height of the element in advance.

Can’t we use -50% instead of half the width and height?

Unfortunately, we’re unable to do that here because percentages doesn’t behave the same as pixel values when used with the CSS mask-position property. They’re tricky.

I have a detailed Stack Overflow answer that explains the difference. It deals with background-position but the same logic applies to the CSS mask-position property.

However, we can use some tricks to make it work with percentage values and without the need to know the width or the height. When a gradient (or a background layer) has a width and height equal to the element, we cannot move it using percentage values. So we need to change its size!

I will define a size equal to 99.5% 99.5%. I am reducing 0.5% from the width and the height to have a value different from 100% and at the same time keep the same visual result since we won’t notice a big difference between 100% and 99.5%. Now that our gradient has a size different from 100% we can move it using percentage values.

I will not detail all the math, but to move it by half the width and the height we need to use this equation:

100% * (50/(100 - 99.5)) = 100% * 100 = 10000%

It’s a strange value but it does the job:

As you can see, the trick works just fine. Whatever the size of the element is, we can cut four corners using only one gradient. However, this method has a small drawback when the width or the height of the element is a decimal value. Here is an example with an image having a width equal to 150.5px:

The use of 99.5% combined with 150.5px will create rounding issues that will break the calculation, resulting in the mask being misaligned. So, use this method with caution.

To overcome the rounding issue, we can combine the last trick with a pseudo-element. Here is a step-by-step illustration to understand the idea:

Here’s what going on in there:

  1. We define a pseudo-element that behaves as our background layer. Logically, we should use inset:0 to make it cover the entire area, but we will create a small overflow by using inset: -10% meaning that the pseudo element will overflow each side by 10%.
  2. We set our CSS mask to the pseudo-element. The mask size needs to match the size of the main element, not the pseudo-element. In other words, it will be smaller than the size of the pseudo-element and this is what we want to be able to move using percentage values. After we do the math, the size needs to be 100%/1.2. Notice in the demo above that the CSS mask is within the green border so that it matches the size of the container.
  3. Now, we need to move it in a way that simulates cutting the corner of the main element. The center of the hole needs to be in the corner of the main element, as illustrated in the demo. To do this, we use mask-position: 300% 300% ( 300% = 50%/(1 - 1/1.2) ).
  4. We remove no-repeat to activate the repetition and get the same effect for every corner.
  5. We clip the overflow and we get our final result!

I know it’s a bit overkill, but it does work and it requires only one gradient instead of four.

Let’s quickly recap the three methods we just covered:

  • The first method uses four gradients and has no drawbacks as far as usage. Sure, it’s verbose but it works with any kind of element and size. I recommend using this one.
  • The second method uses one gradient and works with any element, but it can break in some particular cases. It’s suitable with fixed-size elements. It’s ok to use, but maybe less frequently.
  • The third method uses one gradient and requires a pseudo-element. It won’t work with <img> and other elements that unable to support a pseudo-element.

The generator only supports the first and third methods.

Now that we saw the case with all the corners, let’s disable some of them. Using the first method, any corner we want to keep uncut we simply remove its gradient and adjust the size of what remains.

To disable the top-right corner:

  • We remove the top-right gradient (the blue one).
  • We have an empty corner, so we increase the size of the red gradient (or the purple one) to cover that leftover space.

Done!

You probably see just how many possibilities and combinations we can do here. If we want to cut N corners (where N ranges from 1 to 4), we use N gradients. All we need is to correctly set the size of each one to leave no space.

What about the other methods where there’s only one gradient? We will need another gradient! Those two methods use only one radial-gradient() to cut the corners, so we will rely on another gradient to “hide” the cut. We can use a conic-gradient() with four sections for this task:

conic-gradient(red 25%, blue 0 50%, green 0 75%, purple 0)

We add it on the top of the radial gradient to get the following:

The conic-gradient() covers the radial-gradient() and no corner is cut. Let’s change one color in the conic-gradient() to transparent. The one at the top-right, for example:

Did you see that? We revealed one corner of the radial-gradient() and we end with one cut corner!

Now let’s do the same thing, but for the bottom-left corner.

I think you probably get the trick by now. By changing the colors of the conic-gradient() from opaque to transparent, we reveal the corners we want to cut and gain all kinds of possible combinations. The same can be done with the third method.

Circular border-only cut-out

Let’s make the border-only version of the previous shape. In other words, we achieve the same shape but knock out the fill so all we’re left with is a border of the shape.

This is a bit tricky because we have different cases with different code. Fair warning, I will be using a lot of gradients here while finding opportunities to trim the number of them.

It should be noted that we will consider a pseudo-element in this case. Showing only the border means we need to hide the inner “fill” of the shape. Applying this to the main element will also hide the content — that’s why this is a nice use case for a pseudo-element.

One cut corner

This one needs one radial gradient and two conic gradients:

The first example illustrates the radial gradient (in red) and both conic gradients (in blue and green). In the second example, we apply all of them inside the CSS mask property to create the border-only shape with one cut corner.

Diagram zoomed in on two corners of a rectangle and another where the CSS mask is placed.
Here’s a diagram of the game plan.

As the diagram shows, the radial-gradient() creates the quarter of a circle and each conic-gradient() creates two perpendicular segments to cover two sides. It should be noted that overlapping gradients is not an issue since we are not going to change the CSS mask-composite property value.

Using the same code an adjusting a few variables, we can get the shape for the other corners.

Two cut corners

For the two-corner configuration we have two situations taking place.

In the first situation, there are two opposite corners where we need two radial gradients and two conic gradients.

The configuration is almost the same as cutting only one corner: we add an extra gradient and update a few variables.

In the second situation, there are two adjacent corners and, in this case, we need two radial gradients, one conic gradient, and one linear gradient.

“Wait!” you might exclaim. “How come the conic gradient covers three sides?” If you check the code, notice the repeat-y. In all of the examples, we always used no-repeat for the gradients, but for this we can repeat one of them to cover more sides and reduce the number of gradients we use.

Here is an example with only the conic-gradient() to understand the repetition. The trick is to have a height equal to 100% minus the border size so that the gradient fills that space when repeating, which covers the third side in the process.

Three cut corners

For this configuration, we need three radial gradients, one conic gradient, and two linear gradients.

Four corners cut

It takes four radial gradients and two linear gradients to cut all four corners.

I can hear you screaming, “How the heck am I supposed to memorize all these cases?!” You don’t need to memorize anything since you can easily generate the code for each case using the online generator. All you need is to understand the overall trick rather than each individual case. That’s why I’ve only gone into fine detail on the first configurations — the rest are merely iterations that tweak the initial foundation of the trick.

Notice there’s a general pattern we’ve been following throughout the examples:

  1. We add a radial-gradient() on the corners we want to cut.
  2. We fill the sides using either a conic-gradient() or a linear-gradient() to create the final shape.

It should be noted that we can find different ways to create the same shape. What I am showing in this post are the methods I found to be best after trying lots of other ideas. You may have a different approach you consider to be better! If so, definitely share it in the comments!

Angled cut-out

Let’s tackle another type of cut shape: the angled cut.

We have two parameters: the size and angle of the cut. To get the shape, we need a conic-gradient() for each corner. This configuration is very similar to the example that kicked off this article.

Here is an illustration of one corner to understand the trick:

The difference between each corner is an extra offset of 90deg in from and the at position. The full code is like below:

--size: 30px;
--angle: 130deg;

--g: #0000 var(--angle), #000 0;
mask:
  conic-gradient(from calc(var(--angle)/-2 -  45deg) 
    at top    var(--size) left  var(--size),var(--g)) top left,
  conic-gradient(from calc(var(--angle)/-2 + 45deg) 
    at top    var(--size) right var(--size),var(--g)) top right,
  conic-gradient(from calc(var(--angle)/-2 - 135deg) 
    at bottom var(--size) left  var(--size),var(--g)) bottom left,
  conic-gradient(from calc(var(--angle)/-2 + 135deg) 
    at bottom var(--size) right var(--size),var(--g)) bottom right;
mask-size: 51% 51%;
mask-repeat: no-repeat;

If we want to disable one corner, we remove the conic-gradient() for that corner and update the size of another one to fill the remaining space exactly like we did with the circular cut. Here’s how that looks for one corner:

We can do the exact same thing for all the other corners to get the same effect.

In addition to CSS mask, we can also use the CSS clip-path property to cut the corners. Each corner can be defined with three points.

Zooming in on a corner of the shape showing the three points that form the angled cut.
The shape consists of two points at each end of the cut, and one between them to form the angle.

The other corners will have the same value with an offset of 100%. This gives us the final code with a total of 12 points — three per corner.

/* I will define T = [1-tan((angle-90)/2)]*size */
clip-path: polygon(
  /* Top-left corner */
  0 T, size size,0 T, /* OR 0 0 */
  /* Top-right corner */
  calc(100% - T) 0,calc(100% - size) size,100% T, /* OR  100% 0 */
  /* Bottom-right corner*/
  100% calc(100% - T),calc(100% - size) calc(100% - size), calc(100% - T) 100%, /* OR 100% 100% */
  /* Bottom-left corner */ 
  T 100%, size calc(100% - size),0 calc(100% - T) /* OR 0 100% */
)

Notice the OR comments in that code. It defines the code we have to consider if we want to disable a particular corner. To cut a corner, we use three points. To uncut a corner, we use one point — which is nothing but the coordinate of that corner.

Border-only angled cut

Oof, we have reached the last and trickiest shape at last! This one can be achieved with either gradients or clip-path, but let’s go with the clip-path approach.

Things would get complex and verbose if we go with the gradient approach. Here’s a demo that illustrates that point:

There are nine gradients total, and I am still not done with the calculation. As you can tell, the thickness of the border is incorrect, plus the final result is unsatisfying due to the nature of gradients and their anti-aliasing issues. This approach might be a good exercise to push the limit of gradients, but I don’t recommend it in a production environment.

So, back to the clip-path method. We will still wind up with verbose code, but less of a big deal since the generator can do the job for us with a cleaner end result.

Here is an overview of the path. I am adding a small gap to better see the different points but we should have an overlap of points instead.

We have 13 outer points (the ones in black) and 13 inner points (the ones in blue).

The way we calculate the outer points is the same as how we did it for the regular angled cut. For the inner points, however, we need more math. Don’t worry, I’ll spare you some “boring” geometry explanation for this one. I know most of you don’t want it, but in case you need to dig into this, you can check the JavaScript file of the generator to find the code and the math I am using to generate the shape.

The 180deg special case

Before we end, there’s a special case for the angle cut I want to call out. It’s where we use an angle equal to 180deg. Here’s what that produces:

We have a straight line on the corner so we can optimize the clip-path code. For the full shape, we can use eight points (two points per corner) instead of 12. And for the border-only version, we can use 18 points (nine inner points and outer points) instead of 26. In other words, we can remove the middle point.

The border-only shape can also be made using gradients. But rather than using nine gradients like we did before, we can get away with only four linear gradients and a clean result.

Conclusion

We just combined CSS masks with gradients to create some fancy shapes without resorting to hacks and a lot of code! We also experienced just how much it takes to strike the right balance of code to get the right results. We even learned a few tricks along the way, like changing values by one or even half a unit. CSS is super powerful!

But, as we discussed, the online generator I made is a great place to get the code you need rather than writing it out by hand. I mean, I went through all the work of figuring out how all of this works and I would likely still need to reference this very article to remember how it’s all put together. If you can memorize all of this, kudos! But it’s nice to have a generator to fall back on.


Tricks to Cut Corners Using CSS Mask and Clip-Path Properties originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/feed/ 0 364279
6 Useful Bookmarklets to Boost Web Development https://css-tricks.com/web-development-bookmarklets/ https://css-tricks.com/web-development-bookmarklets/#comments Mon, 28 Mar 2022 18:15:41 +0000 https://css-tricks.com/?p=364118 A bookmarklet is a JavaScript-based bookmark that adds to a web browser. I’d like to show you some awesome web browser hacks to aid your web development workflow and how to convert those hacks into time-saving bookmarklets.

  1. Activating design mode


6 Useful Bookmarklets to Boost Web Development originally published on CSS-Tricks. You should get the newsletter.

]]>
A bookmarklet is a JavaScript-based bookmark that adds to a web browser. I’d like to show you some awesome web browser hacks to aid your web development workflow and how to convert those hacks into time-saving bookmarklets.

  1. Activating design mode
  2. Applying a background to everything
  3. Simulating events
  4. Setting cookies
  5. Toggling classes
  6. Color widget bookmark
  7. What other bookmarklets can you think of?

Activating design mode

Design mode (styled as designMode since it’s a JavaScript property) is for who like to experiment with variations of copy on a live website. For example, copywriters who like to observe how content reads within the flow of the website’s design, or, say, designers who want to ensure that text fits comfortably within a certain space at a certain font size.

JavaScript has a mightily simple feature that can render an entire HTML document editable. It works exactly like HTML’s contenteditable="true" name-value attribute (or contentEditable="true" in JavaScript) but for the whole document. If you’d like to see how it works, start by entering the browser’s console using the relevant keyboard shortcut:

  • Chrome: Option + + J / Shift + CTRL + J
  • Firefox: Option + + K / Shift + CTRL + K
  • Safari: Option + + C / Shift + CTRL + C

Next, type document.designMode="on" into the console, hit Return, and then click on any text element. You’ll see that this text element (and all other text elements) are now editable simply by clicking on them. This method of editing text on a live website is much faster than having to open DevTools, then right-clicking and selecting the “Edit Text” option… and much less tiresome.

Showing an edited version of the CSS-Tricks guide landing page using the design mode bookmarklet.
“Guides and Thangs” — my favorite part of CSS-Tricks

While I’m not sure that “design mode” is the most accurate description of the feature, it’s super useful nonetheless and it’s actually been around for a really long time, surprisingly.

And what’s even an even faster way to enable it? A bookmarklet, of course! Create a bookmark using javascript: document.designMode="on";void 0; as the URL.

Showing the bookmarklet installation.

Applying a background to everything

When HTML elements don’t have backgrounds, it can be difficult to visualize their bounds and/or accurately measure the distance between them and other elements. Developers might want to better visualize bounds when dealing with optical imbalance (i.e. when something “looks off” even though it’s not), margin collapse (when certain margins are ignored), various issues with display:/float:/position:, and more.

Applying backgrounds means applying a semi-transparent background to all HTML elements in order to better visualize their bounds and spacings. It’s something many of us commonly do by opening up DevTools then typing a CSS declaration like selector { background: rgb(0 0 0 / 10%); } into the “Styles” box. But again, it’s really tiresome and repetitive — and something we can simplify with a bookmarklet.

Once again, to create a bookmark, we’re going to make a URL. Here’s what we can use for this one:

javascript: document.querySelectorAll("*").forEach(element => element.style.background="rgb(0 0 0 / 10%)");

We’re using a semi-transparent background because the transparency stacks, which ensures that every nested element is distinguishable and the distances between them can be measured.

Showing the CSS-Tricks guides landing page with all backgrounds fill with varying shades of gray.
Apply a background to everything to see what’s happening.

Simulating events

Have you ever had to test a web event that first requires a series of interactions, or certain conditions to be met? It’s super time-consuming to have to test or debug these kinds of functionalities. This event simulation bookmarklet can be used to instantly trigger specific events, making testing a breeze.

Simulating an event means coding a “throwaway” button that triggers a JavaScript event, making it much easier to quickly and repeatedly test the event without having to meet any usual user-facing conditions, like needing to be logged in.

Assuming that you have your JavaScript event listeners set up, create a bookmark for each event that you’d like to trigger/simulate and submit the following URL:

javascript: document.querySelector("SELECTOR").click();

Replace “SELECTOR” with your unique selector, replace “click” with “focus” or “blur” (when necessary), or extend the snippet to make it trigger more complex events, like scroll.

Setting cookies

Cookies are tokens that are stored on a website visitor’s computer by the website that they’re visiting. Cookies contain data that can be read by the website that created them until they’ve exceeded their expiration date or have been deleted. The mere existence of a cookie can determine whether or not a visitor is logged in, whereas the data itself can store user information.

An example of a scenario where you might want to set a cookie using a bookmarklet is when you want to force a logged-in state during website testing. Websites often look very different for users that are logged in, however, logging in and out eventually becomes very tedious, so this bookmarklet can save quite a bit of time.

Manually writing expires= dates for cookies is awkward as heck, but luckily this create-your-own-set-cookie-bookmarklet app can generate a bookmarklet for a specific cookie, if you know its exact name.

Toggling classes

You may want to add or remove a class from an HTML element in order to trigger a fresh state or a change in appearance, otherwise known as toggling classes. Class toggling happens behind the scenes of most live websites, but it can also be used during testing to skip having to meet certain user-facing conditions.

Class toggling can be used to trigger changes in appearance (e.g. alternative themes or states) and even animations, but it can be a little fiddly when doing it with developer tools when it’s only for testing reasons (i.e. the website doesn’t actually function that way for users). Similar to the other bookmarklets, use this one to rapidly toggle classes and save yourself time.

Create the following bookmarklet to target all elements that match your chosen “SELECTOR”, which, in turn, toggles the “CLASS.”

javascript: document.querySelectorAll("SELECTOR").forEach(element => element.classList.toggle("CLASS"));

Color widget bookmark

While not technically a “bookmarklet,” this bookmarkable data URI by Scott Jehl opens up an <input type="color"> in a new tab:

data:text/html;charset=utf-8,%3Chtml%3E%3Ctitle%3EColor Picker%3C%2Ftitle%3E%3Cinput type%3D"color"%3E%3C%2Fhtml%3E

Why is that cool? Well, how many times have you needed to grab a color value off a page, only to find yourself cracking open DevTools, clicking through a bunch of elements, and pouring over CSS properties to find the value? Better to run this little guy, click the element, and get a color right away!

What other bookmarklets can you think of?

Are there any overly repetitive web development workflows that require you to use the web browser’s sometimes-awkward developer tools? If so, it’s super easy to create your own time-saving bookmarklets. Just remember to start the URL with javascript:!

And if you’ve made a bookmarklet to simplify your workflow, I’d love to see it! Share them here in the comments and let’s get a nice collection going.


6 Useful Bookmarklets to Boost Web Development originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/web-development-bookmarklets/feed/ 20 364118
Write HTML, the HTML Way (Not the XHTML Way) https://css-tricks.com/write-html-the-html-way-not-the-xhtml-way/ https://css-tricks.com/write-html-the-html-way-not-the-xhtml-way/#comments Mon, 21 Mar 2022 20:08:38 +0000 https://css-tricks.com/?p=363745 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 …


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

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

]]>
https://css-tricks.com/write-html-the-html-way-not-the-xhtml-way/feed/ 21 363745
Optimizing SVG Patterns to Their Smallest Size https://css-tricks.com/optimizing-svg-patterns/ https://css-tricks.com/optimizing-svg-patterns/#comments Fri, 18 Mar 2022 22:31:22 +0000 https://css-tricks.com/?p=364090 I recently created a brick wall pattern as part of my #PetitePatterns series, a challenge where I create organic-looking patterns or textures in SVG within 560 bytes (or approximately the size of two tweets). To fit this constraint, I have …


Optimizing SVG Patterns to Their Smallest Size originally published on CSS-Tricks. You should get the newsletter.

]]>
I recently created a brick wall pattern as part of my #PetitePatterns series, a challenge where I create organic-looking patterns or textures in SVG within 560 bytes (or approximately the size of two tweets). To fit this constraint, I have gone through a journey that has taught me some radical ways of optimizing SVG patterns so that they contain as little code as possible without affecting the overall image quality.

I want to walk you through the process and show you how we can take an SVG pattern that starts at 197 bytes all the way down to a mere 44 bytes — a whopping 77.7% reduction!

The SVG pattern

This is what’s called a “running bond” brick pattern. It’s the most common brick pattern out there, and one you’ve surely seen before: each row of bricks is offset by one half the length of a brick, creating a repeating staggered pattern. The arrangement is pretty simple, making SVG’s <pattern> element a perfect fit to reproduce it in code.

The SVG <pattern> element uses a pre-defined graphic object which can be replicated (or “tiled”) at fixed intervals along the horizontal and vertical axes. Essentially, we define a rectangular tile pattern and it gets repeated to paint the fill area.

First, let’s set the dimensions of a brick and the gap between each brick. For the sake of simplicity, let’s use clean, round numbers: a width of 100 and a height of 30 for the brick, and 10 for the horizontal and vertical gaps between them.

Showing a highlighted portion of a brick wall pattern, which is the example we are using for optimizing SVG patterns.

Next, we have to identify our “base” tile. And by “tile” I’m talking about pattern tiles rather than physical tiles, not to be confused with the bricks. Let’s use the highlighted part of the image above as our pattern tile: two whole bricks in the first row, and one whole sandwiched between two half bricks in the second row. Notice how and where the gaps are included, because those need to be included in the repeated pattern tile.

When using <pattern>, we have to define the pattern’s width and height, which correspond to the width and height of the base tile. To get the dimensions, we need a little math:

Tile Width  = 2(Brick Width) + 2(Gap) = 2(100) + 2(10) = 220
Tile Height = 2(Bright Height) + 2(Gap) = 2(30) + 2(10) = 80

Alright, so our pattern tile is 220✕80. We also have to set the patternUnits attribute, where the value userSpaceOnUse essentially means pixels. Finally, adding an id to the pattern is necessary so that it can be referenced when we are painting another element with it.

<pattern id="p" width="220" height="80" patternUnits="userSpaceOnUse">
  <!-- pattern content here -->
</pattern>

Now that we have established the tile dimensions, the challenge is to create the code for the tile in a way that renders the graphic with the smallest number of bytes possible. This is what we hope to end up with at the very end:

The bricks (in black) and gaps (in white) of the final running bond pattern

Initial markup (197 bytes)

The simplest and most declarative approach to recreate this pattern that comes to my mind is to draw five rectangles. By default, the fill of an SVG element is black and the stroke is transparent. This works well for optimizing SVG patterns, as we don’t have to explicitly declare those in the code.

Each line in the code below defines a rectangle. The width and height are always set, and the x and y positions are only set if a rectangle is offset from the 0 position.

<rect width="100" height="30"/>
<rect x="110" width="100" height="30"/>
<rect y="40" width="45" height="30"/>
<rect x="55" y="40" width="100" height="30"/>
<rect x="165" y="40" width="55" height="30"/>

The top row of the tile contained two full-width bricks, the second brick is positioned to x="110" allowing 10 pixels of gap before the brick. Similarly there’s 10 pixels of gap after, because the brick ends at 210 pixels (110 + 100 = 210) on the horizontal axis even though the <pattern> width is 220 pixels. We need that little bit of extra space; otherwise the second brick would merge with the first brick in the adjacent tile.

The bricks in the second (bottom) row are offset so the row contains two half bricks and one whole brick. In this case, we want the half-width bricks to merge so there’s no gap at the start or the end, allowing them to seamlessly flow with the bricks in adjoining pattern tiles. When offsetting these bricks, we also have to include half gaps, thus the x values are 55 and 165, respectively.

Element reuse, (-43B, 154B total)

It seems inefficient to define each brick so explicitly. Isn’t there some way to optimize SVG patterns by reusing the shapes instead?

I don’t think it’s widely known that SVG has a <use> element. You can reference another element with it and render that referenced element wherever <use> is used. This saves quite a few bytes because we can omit specifying the widths and heights of each brick, except for the first one.

That said, <use> does come with a little price. That is, we have to add an id for the element we want to reuse.

<rect id="b" width="100" height="30"/>
<use href="#b" x="110"/>
<use href="#b" x="-55" y="40"/>
<use href="#b" x="55" y="40"/>
<use href="#b" x="165" y="40"/>

The shortest id possible is one character, so I chose “b” for brick. The <use> element can be positioned similarly to <rect>, with the x and y attributes as offsets. Since each brick is full-width now that we’ve switched to <use> (remember, we explicitly halved the bricks in the second row of the pattern tile), we have to use a negative x value in the second row, then make sure the last brick overflows from the tile for that seamless connection between bricks. These are okay, though, because anything that falls outside of the pattern tile is automatically cut off.

Can you spot some repeating strings that can be written more efficiently? Let’s work on those next.

Rewriting to path (-54B, 100B total)

<path> is probably the most powerful element in SVG. You can draw just about any shape with “commands” in its d attribute. There are 20 commands available, but we only need the simplest ones for rectangles.

Here’s where I landed with that:

<path d="M0 0h100v30h-100z
         M110 0h100v30h-100
         M0 40h45v30h-45z
         M55 40h100v30h-100z
         M165 40h55v30h-55z"/>

I know, super weird numbers and letters! They all have meaning, of course. Here’s what’s happening in this specific case:

  • M{x} {y}: Moves to a point based on coordinates.
  • z: Closes the current segment.
  • h{x}: Draws a horizontal line from the current point, with the length of x in the direction defined by the sign of x. Lowercase x indicates a relative coordinate.
  • v{y}: Draws a vertical line from the current point, with the length of y in the direction defined by the sign of y. Lowercase y indicates a relative coordinate.

This markup is much more terse than the previous one (line breaks and indentation whitespace is only for readability). And, hey, we’ve managed to cut out half of the initial size, arriving at 100 bytes. Still, something makes me feel like this could be smaller…

Tile revision (-38B, 62B total)

Doesn’t our pattern tile have repeating parts? It’s clear that in the first row a whole brick is repeated, but what about the second row? It’s a bit harder to see, but if we cut the middle brick in half it becomes obvious.

The left half preceding the red line is the same as the right side.

Well, the middle brick isn’t exactly cut in half. There’s a slight offset because we also have to account for the gap. Anyways, we just found a simpler base tile pattern, which means fewer bytes! This also means we have to halve the width of our <pattern> element from 220 to 110.

<pattern id="p" width="110" height="80" patternUnits="userSpaceOnUse">
  <!-- pattern content here -->
</pattern>

Now let’s see how the simplified tile is drawn with <path>:

<path d="M0 0h100v30h-100z
         M0 40h45v30h-45z
         M55 40h55v30h-55z"/>

The size is reduced to 62 bytes, which is already less than a third of the original size! But why stop here when there’s even more we can do!

Shortening path commands (-9B, 53B total)

It’s worth getting a little deeper into the <path> element because it provides more hints for optimizing SVG patterns. One misconception I’ve had when working with <path> is regarding how the fill attribute works. Having played a lot with MS Paint in my childhood, I’ve learned that any shape I want to fill with a solid color has to be closed, i.e. have no open points. Otherwise, the paint will leak out of the shape and spill over everything.

In SVG, however, this is not true. Let me quote the spec itself:

The fill operation fills open subpaths by performing the fill operation as if an additional “closepath” command were added to the path to connect the last point of the subpath with the first point of the subpath.

This means we can omit the close path commands (z), because the subpaths are considered automatically closed when filled.

Another useful thing to know about path commands is that they come in uppercase and lowercase variations. Lowercase letters mean that relative coordinates are used; uppercase letters mean absolute coordinates are used instead.

It’s a little trickier than that with the H and V commands because they only include one coordinate. Here’s how I would describe these two commands:

  • H{x}: Draws a horizontal line from the current point to coordinate x.
  • V{y}: Draws a vertical line from the current point to coordinate y.

When we are drawing the first brick in the pattern tile, we start from the (0,0) coordinates. We then draw a horizontal line to (100,0) and a vertical line to (100,30), and finally, draw a horizontal line to (0,30). We used the h-100 command in the last line, but it is the equivalent of H0, which is two bytes instead of five. We can replace two similar occurrences and pare the code of our <path> down to this:

<path d="M0 0h100v30H0
         M0 40h45v30H0
         M55 40h55v30H55"/>

Another 9 bytes shaved off — how much smaller can we go?

Bridging (-5B, 48B total)

The longest commands standing in our way of a fully-optimized SVG pattern are the “move to” commands which take up 4, 5, and 6 bytes, respectively. One constraint we have is that:

A path data segment (if there is one) must begin with a “moveto” command.

But that’s okay. The first one is the shortest anyways. If we swap the rows, we can come up with a path definition where we only have to move either horizontally or vertically between the bricks. What if we could use the h and v commands there instead of M?

The path starts from the red dot in the top-left corner. Red are the path commands supported with arrows, black are the coordinates the arrows point to.

The above diagram shows how the three shapes can be drawn with a single path. Note that we are leveraging the fact that the fill operation automatically closes the open part between (110,0) and (0,0). With this rearrangement, we also moved the gap to the left of the full-width brick in the second row. Here’s how the code looks, still broken into one brick per line:

<path d="M0 0v30h50V0
         h10v30h50
         v10H10v30h100V0"/>

Surely, we’ve found the absolute smallest solution now that we’re down to 48 bytes, right?! Well…

Digit trimming (-4B, 44B total)

If you can be a bit flexible with the dimensions, there’s another little way we can optimize SVG patterns. We’ve been working with a brick width of 100 pixels, but that’s three bytes. Changing it to 90 means one less byte whenever we need to write it. Similarly, we used a gap of 10 pixels — but if we change it to 8 instead, we save a byte on each of those occurrences.

<path d="M0 0v30h45V0
         h8v30h45
         v8H8v30h90V0"/>

Of course, this also means we have to adjust the pattern dimensions accordingly. Here’s the final optimized SVG pattern code:

<pattern id="p" width="98" height="76" patternUnits="userSpaceOnUse">
  <path d="M0 0v30h45V0h8v30h45v8H8v30h90V0"/>
</pattern>

The second line in the above snippet — not counting the indentations — is 44 bytes. We got here from 197 bytes in six iterations. That’s a chunky 77.7% size reduction!

I’m wondering though… is this really the smallest size possible? Have we looked at all possible ways to optimize SVG patterns?

I invite you to try and further minify this code, or even experiment with alternative methods for optimizing SVG patterns. I would love to see if we could find the true global minimum with the wisdom of the crowd!

More on creating and optimizing SVG patterns

If you are interested to learn more about creating and optimizing SVG patterns, read my article about creating patterns with SVG filters. Or, if you want to check out a gallery of 60+ patterns, you can view the PetitePatterns CodePen Collection. Lastly, you’re welcome to watch my tutorials on YouTube to help you get even deeper into SVG patterns.


Optimizing SVG Patterns to Their Smallest Size originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/optimizing-svg-patterns/feed/ 10 364090
CSS-Tricks is joining DigitalOcean! https://css-tricks.com/css-tricks-is-joining-digitalocean/ https://css-tricks.com/css-tricks-is-joining-digitalocean/#comments Tue, 15 Mar 2022 12:18:44 +0000 https://css-tricks.com/?p=365042 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


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

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

Digital Ocean logo + CSS-Tricks logo under the sea with little fishies.

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.

]]>
https://css-tricks.com/css-tricks-is-joining-digitalocean/feed/ 126 365042
Say Hello to selectmenu, a Fully Style-able select Element https://css-tricks.com/the-selectmenu-element/ https://css-tricks.com/the-selectmenu-element/#comments Thu, 03 Mar 2022 15:35:32 +0000 https://css-tricks.com/?p=363972 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 …


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

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

An animated screenshot showing a selectmenu element with emojis as options against a bright teal background.

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.

Showing the default UI of the select element in Safari.
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

The Open UI logo, which is a green oval with a rounded fork-like shape with three prongs inside.

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.

Showing the boundaries of a selectmenu element.
  • <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:

A styled selectmenu element with a red button background and a red border around the listbox.

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

A styled selectmenu with a bright pink open button and a box-shadow around the listbox.

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">
    <popup behavior="listbox">
      <option>Option 1</option>
      <option>Option 2</option>
      <option>Option 3</option>
      <option>Option 4</option>
      <option>Option 5</option>
    </popup>
  </div>
</selectmenu>

Interestingly, the <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 <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:

A styled selectmenu where the list box is split into two columns.

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">
    <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>
    </popup>
  </div>
</selectmenu>

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

A styled selectmenu that contains options containing sub-options in the listbox.

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>
  <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>
  </popup>
  `
</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.

]]>
https://css-tricks.com/the-selectmenu-element/feed/ 12 363972
Build Membership Businesses with Memberful https://css-tricks.com/build-membership-businesses-with-memberful/ https://css-tricks.com/build-membership-businesses-with-memberful/#comments Thu, 03 Mar 2022 15:33:57 +0000 https://css-tricks.com/?p=364001 (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 …


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

]]>
(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.

]]>
https://css-tricks.com/build-membership-businesses-with-memberful/feed/ 1 364001
7 Fresh Links on Performance For March 2022 https://css-tricks.com/performance-links-february-2022/ https://css-tricks.com/performance-links-february-2022/#respond Wed, 02 Mar 2022 21:26:50 +0000 https://css-tricks.com/?p=364350 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.

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

Screenshot of the new WebPageTest homepage, a tool for testing performance metrics.
The new WebPageTest website design

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

]]>
https://css-tricks.com/performance-links-february-2022/feed/ 0 364350
How to Make a “Raise the Curtains” Effect in CSS https://css-tricks.com/css-raise-the-curtains-effect/ https://css-tricks.com/css-raise-the-curtains-effect/#comments Wed, 02 Mar 2022 15:57:32 +0000 https://css-tricks.com/?p=363882 “Raise the curtains” is what I call an effect where the background goes from dark to light on scroll, and the content on top also goes from light to dark while in a sticky position.

Here’s an example where I …


How to Make a “Raise the Curtains” Effect in CSS originally published on CSS-Tricks. You should get the newsletter.

]]>
“Raise the curtains” is what I call an effect where the background goes from dark to light on scroll, and the content on top also goes from light to dark while in a sticky position.

Here’s an example where I used the effect on a real-life project:

Want to know how it’s done? I will take you behind the curtain and show you how to raise it, with nothing but HTML and CSS.

Let’s start with the HTML

What we’re making is sort of a simplified “raise the curtain” effect like this:

Showing the raise the curtains effect from dark blue to wheat.
The background and text both change color while scrolling over the element.

I’m keeping things simple for the sake of clarity, but we can stub this out with three elements:

<div class="curtain">
  <div class="invert">
    <h2>Section title</h2>
  </div>
</div>

First, we need a container for the curtain, which we’ll give a .curtain class. Then, inside the .curtain, we have the an .invert child element that will serve as our “sticky” box. And, finally, we have the content inside this box — a good old-fashioned <h2> element for this specific example.

Let’s set up some CSS variables

There are three values we know we’ll need upfront. Let’s make CSS variables out of them so it’s easy to write them into our styles and easily change them later if we need to.

  • --minh – The height of the container
  • --color1 – The light color
  • --color2 – The dark color
:root {
  --minh: 98vh;
  --color1: wheat;
  --color2: midnightblue;
}

Time to draw the curtain

Next, we can define our .curtain element using the following techniques:

  • A linear-gradient for the “split” background
  • min-height for the extra space at the bottom of the container

We use the ::after pseudo-element to add the extra space to the bottom. This way, our “sticky” content will actually stick to the container while scrolling past the ::after element. It’s an illusion.

.curtain {
  /** create the "split" background **/
  background-image: linear-gradient(to bottom, var(--color2) 50%, var(--color1) 50%);
}

/** add extra space to the bottom (need this for the "sticky" effect) **/
.curtain::after {
  content: "";
  display: block;
  min-height: var(--minh);
}

Making sticky content

Next up, we need to make our content “sticky” in the sense that it sits perfectly inside the container as the background and text swap color values. In fact, we already gave the .curtain‘s child element an .invert class that we can use as the sticky container.

Stay with me for a moment — here’s how this is going to play out:

  • position: sticky and top define the stickiness and where it sticks.
  • mix-blend-mode: difference blends the color of the content inside the <h2> element into the .curtain‘s background gradient.
  • display: flex centers the content for presentation.
  • min-height defines the height of the container and allows for the extra space at the bottom.
  • color sets the color of the h2 heading.

Now to put that into CSS code!

.invert {
  /** make the content sticky **/
  position: sticky;
  top: 20px;

  /** blend the content with the contrast effect **/
  mix-blend-mode: difference;

  /** center the content **/
  display: flex;
  align-items: center;
  justify-content: center;
  
  /** set the minimum height of the section **/
  min-height: var(--minh);
}

h2 {
  /** set the color of the text **/
  color: var(--color1);
}

There are many things going on here, so let’s explain each one of them.

First, we have a sticky position that is self-explanatory and flexbox to help center the content. Nothing new or particularly tricky about this.

The content’s height is set using CSS variable and the value is the same height value as the .curtain::after pseudo-element.

The mix-blend-mode: difference declaration blends our content with the background. The difference value is complicated, but you might visualize it like inverted text color against the background. Here’s a nice demo from the CSS-Tricks Almanac showing off the different mix-blend-mode values:

To make the blending work, we need to set the color of our heading. In this case, we’re assigning a light color value (wheat) to the --color1 variable.

“Raise the Curtains” Demo

Gotchas

I experienced a few problems while working out the details of the “raise the curtain” effect. If you want to add images to the “sticky” content, for example, avoid using images that don’t look good when their colors are inverted. Here’s a quick demo where I made a simple SVG and transparent PNG image, and it looks good.

Another gotcha: there’s no way to set mix-blend-mode: difference on specific child elements, like headings, while avoiding the effect on images. I discovered there are several reasons why it doesn’t work, the first of which is that position: sticky cancels the blending.

The same goes when using something like transform: skewY on the container to add a little “tilt” to things. I suspect other properties don’t play well with the blending, but I didn’t go that far to find out which ones.

Here’s the demo without scrolling that removes the troubling properties:

Curtain call!

I enjoyed building this component, and I always love it when I can accomplish something using only HTML and CSS, especially when they work smoothly on every browser.

What will make with it? Is there a different way you would approach a “raise the curtain” effect like this? Let me know in the comments!


How to Make a “Raise the Curtains” Effect in CSS originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/css-raise-the-curtains-effect/feed/ 2 363882
CSS Custom Highlight API: The Future of Highlighting Text Ranges on the Web https://css-tricks.com/css-custom-highlight-api-early-loo/ https://css-tricks.com/css-custom-highlight-api-early-loo/#comments Tue, 01 Mar 2022 17:41:13 +0000 https://css-tricks.com/?p=363724 Styling ranges of text in software is a very useful thing to be able to do. Thankfully, we have the CSS Custom Highlight API to look forward to because it represents the future of styling text ranges on the web.…


CSS Custom Highlight API: The Future of Highlighting Text Ranges on the Web originally published on CSS-Tricks. You should get the newsletter.

]]>
Styling ranges of text in software is a very useful thing to be able to do. Thankfully, we have the CSS Custom Highlight API to look forward to because it represents the future of styling text ranges on the web.

Animation screenshot of the CSS Custom Highlight API demo.

One example: if you’ve ever used text editing software like Google Docs, Word, or Dropbox Paper, you’ll see they detect spelling and grammar errors and displaying nice little squiggly underlines below them to attract attention. Code editors like VS Code do the same for code errors.

Another very common use case for highlighting text is search and highlight, where you’re given a text input box and typing in it searches matching results on the page, and highlights them. Try pressing Ctrl/+ F in your web browser right now and type in some text from this article.

The browser itself often handles these styling situations. Editable areas (like a <textarea>) get spelling squiggles automatically. The find command highlights found text automatically.

But what about when we want to do this type of styling ourselves? Doing this on the web has been a common problem for a long time. It has probably costed many people a lot more time than it should have.

This isn’t a simple problem to solve. We aren’t just wrapping text in a <span> with a class and applying some CSS. Indeed, this requires being able to correctly highlight multiple ranges of text across an arbitrarily complex DOM tree, and possibly crossing the boundaries of DOM elements.

There are two common solutions to this, including:

  1. styling text range pseudo-elements, and
  2. creating your own text highlighting system.

We’ll review them first and then take a look at the upcoming CSS Custom Highlight API that can change it all. but if you’re

Potential Solution #1: Style-able Text Ranges

Probably the most well-known style-able text range is the user selection. When you use your pointing device to select a piece of text in a web page, a Selection object is automatically created. In fact, try selecting text on this page right now, and then run document.getSelection() in the DevTools console. You should see location information about the selected text.

DevTools window showing the position of the current selection in the console.

It turns out that you can also create a text selection programmatically from JavaScript. Here is an example:

// First, create a Range object.
const range = new Range();

// And set its start and end positions.
range.setStart(parentNode, startOffset);
range.setEnd(parentNode, endOffset);

// Then, set the current selection to this range.
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);

The last piece of the puzzle is to style this range. CSS has a pseudo-element called ::selection to do just that, and it’s supported across all browsers.

::selection {
  background-color: #f06;
  color: white;
}

Here is an example using this technique to highlight all words in a page one after the other:

On top of the ::selection pseudo-element, there are a number of other pseudo-elements:

  • ::target-text selects the text that has been scrolled to in browsers that support the scroll-to-text feature. (MDN)
  • ::spelling-error selects text that is flagged by the browser as containing a spelling error. (MDN)
  • ::grammar-error selects text that is flagged by the browser as containing a grammar error. (MDN)

Unfortunately browser support isn’t great here and although these ranges are useful in each of their own right, they can’t be used to style custom pieces of text — only browser-predefined ones

So the user text selection is nice because it’s relatively simple to put in place and doesn’t change the DOM of the page. Indeed, Range objects are essentially coordinates of segments in the page, rather than HTML elements that need to be created to exist.

One major drawback, however, is that creating a selection resets whatever the user has already manually selected. Try selecting text in the demo above to test this. You’ll see how it goes away as soon as the code moves the selection somewhere else.

Potential Solution #2: Custom Highlighting System

This second solution is pretty much the only thing you can do if using the Selection object is insufficient for you. This solution revolves around doing everything yourself, using JavaScript to insert new HTML elements in the DOM where you want the highlighting to appear.

Unfortunately, this means way more JavaScript to write and maintain, not to mention it forces the browser to re-create the layout of the page whenever the highlighting changes. Plus, there are complicated edge cases, for example, when you want to highlight a piece of text that spans across multiple DOM elements.

Illustration showing a line of HTML with an emphasis element and a strong element with a bright yellow highlight running through them.

Interestingly, CodeMirror and Monaco (the JavaScript text editor library that powers VS Code) have their own highlighting logic. They use a slightly different approach where the highlights are contained in a separate part of the DOM tree. The lines of text and the highlighted segments are rendered in two different places in the DOM which are then positioned over each other. If you inspect the DOM sub-tree that contains the text, there are no highlights. This way, the highlights can be re-rendered without impacting the lines of text and having to introduce new elements within them.

Overall, it feels like a browser-powered highlighting feature is missing. Something that would help solve all of these drawbacks (no interference with user text selection, multi-selection support, simple code) and be faster than custom-made solutions.

Fortunately, that’s what we’re here to talk about!

Enter the CSS Custom Highlight API

The CSS Custom Highlight API is a new W3C specification (currently in Working Draft status) that makes it possible to style arbitrary text ranges from JavaScript! The approach here is very similar to the user text selection technique we reviewed earlier. It gives developers a way to create arbitrary ranges, from JavaScript, and then style them using CSS.

Creating Ranges of Text

The first step is to create the ranges of text that you want to highlight. which can be done using a Range in JavaScript. So, like we did when setting the current selection:

const range = new Range();
range.setStart(parentNode, startOffset);
range.setEnd(parentNode, endOffset);

It’s worth noting that the setStart and setEnd methods work differently if the node passed as the first argument is a text node or not. For text nodes, the offset corresponds to the number of characters within the node. For other nodes, the offset corresponds to the number of child nodes within the parent node.

Also worth noting is that setStart and setEnd aren’t the only ways to describe where a range starts and ends. Take a look at the other methods available on the Range class to see other options.

Creating Highlights

The second step consists in creating Highlight objects for the ranges created in that last step. A Highlight object can receive one or more Ranges. So if you want to highlight a bunch of pieces of text in exactly the same way, you should probably create a single Highlight object and initialize it with all of the Ranges that correspond to these pieces of text.

const highlight = new Highlight(range1, range2, ..., rangeN);

But you can also create as many Highlight objects as you need. For example, if you are building a collaborative text editor where each user gets a different text color, then you can create one Highlight object per user. Each object can then be styled differently, as we’ll see next.

Registering Highlights

Now Highlight objects on their own don’t do anything. They first need to be registered in what is called the highlight registry. This is done by using the CSS Highlights API. The registry works like a map where you can register new highlights by giving them names, as well as remove highlights (or even clear the entire registry).

Here is how to register a single highlight.

CSS.highlights.set('my-custom-highlight', highlight);

Where my-custom-highlight is the name of your choosing and highlight is a Highlight object created in the last step.

Styling Highlights

The final step is to actually style the registered highlights. This is done with the new CSS ::highlight() pseudo-element, using the name you chose when registering the Highlight object (which is my-custom-highlight in our example above).

::highlight(my-custom-highlight) {
  background-color: yellow;
  color: black;
}

It’s worth noting that, just like ::selection, a subset of CSS properties only can be used with the ::highlight() pseudo-element:

Updating Highlights

There are multiple ways to update highlighted text on the page.

For example, you can clear the highlight registry altogether with CSS.highlights.clear() and then start again from the beginning. Or, you can also update the underlying ranges without having to re-create any of the objects all. For this, use the range.setStart and range.setEnd methods again (or any of the other Range methods) and the highlights will be re-painted by the browser.

But, the Highlight object works like a JavaScript Set, so this means you also add new Range objects to an existing Highlight with highlight.add(newRange) or remove a Range with highlight.delete(existingRange).

Third, you can also add or remove specific Highlight objects from the CSS.highlights registry. Since this API works like a JavaScript Map, you can set and delete to update the currently registered Highlights.

Browser Support

The specification for the CSS Custom Highlight API is relatively new and its implementation in browsers is still incomplete. So, although this is going to be a very useful addition to the web platform, it’s not quite ready for production use.

The Microsoft Edge team is implementing the CSS Custom Highlight API in Chromium at the moment. In fact, the feature can already be used in Canary versions right now by enabling the Experimental Web Platform features flag (under about:flags). There is currently no firm plan as to when the feature will ship in Chrome, Edge, and other Chromium-based browsers, but it’s getting very close.

The API is also supported in Safari 99+ but behind an experiment flag (Develop → Experimental Features → Highlight API), and the interface is a little bit different in that it uses StaticRange objects instead.

Firefox does not support the API yet, though you can read Mozilla’s position about it for more information.

Demo

Speaking of Microsoft Edge, they have a demo set up where you can take the CSS Custom Highlight API for a test drive. But Before trying the demo, be sure you’re using either Chrome or Edge Canary with the Experimental Web Platform features flag in the about:flags page.

/button View the demo

The demo uses the Custom Highlight API to highlight ranges of text in the page based on what you type in the search field at the top of the page.

After the page loads, JavaScript code retrieves all the text nodes in the page (using a TreeWalker) and when the user types in the search field, the code iterates over these nodes until it finds matches. Those matches are then used to create Range objects, which are then highlighted with the Custom Highlight API.

Closing Thoughts

So, is this new browser-provided highlighting API really worth it? Totally!

For one, even if the CSS Custom Highlight API may seem a bit complicated at first (i.e. having to create ranges, then highlights, then registering them, and finally styling them), it’s still way simpler than having to create new DOM elements and insert them in the right places.

More importantly, browser engines can style these ranges very, very fast.

The reason only a subset of CSS properties is allowed to be used with the ::highlight() pseudo-element is that the subset only contains properties that can be applied by the browser very effectively without having to recreate the layout of the page. Highlighting ranges of text by inserting new DOM elements in the page around them requires the engine to do much more work.

But don’t take my word for it. Fernando Fiori, who worked on the API, created this nice performance comparison demo. On my computer, the CSS Custom Highlight API performs on average 5✕ as fast as the DOM-based highlighting.

With Chromium and Safari experimental support already here, we’re getting close to something that can be used in production. I can’t wait for browsers to support the Custom Highlight API consistently and see what features this will unlock!


CSS Custom Highlight API: The Future of Highlighting Text Ranges on the Web originally published on CSS-Tricks. You should get the newsletter.

]]>
https://css-tricks.com/css-custom-highlight-api-early-loo/feed/ 3 363724
Ahmad Shadeed: Use Cases For CSS fit-content https://css-tricks.com/ahmad-shadeed-use-cases-for-css-fit-content/ Tue, 01 Mar 2022 15:41:16 +0000 https://css-tricks.com/?p=364351 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 …


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

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

Ahmad Shadeed's flow chat illustrating the way browsers handle the CSS fit-content keyword.
“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.

]]>
364351
IE Down, Edge Up… Global Browser Usage Stats Are for Cocktail Parties and Conference Slides https://css-tricks.com/ie-down-edge-up-global-browser-usage-stats-are-for-cocktail-parties-and-conference-slides/ https://css-tricks.com/ie-down-edge-up-global-browser-usage-stats-are-for-cocktail-parties-and-conference-slides/#comments Mon, 28 Feb 2022 23:14:29 +0000 https://css-tricks.com/?p=364346 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 …


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.

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

]]>
https://css-tricks.com/ie-down-edge-up-global-browser-usage-stats-are-for-cocktail-parties-and-conference-slides/feed/ 3 364346
Web Component Pseudo-Classes and Pseudo-Elements are Easier Than You Think https://css-tricks.com/web-component-pseudo-classes-and-pseudo-elements/ https://css-tricks.com/web-component-pseudo-classes-and-pseudo-elements/#comments Mon, 28 Feb 2022 15:37:23 +0000 https://css-tricks.com/?p=363929 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 …


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

]]>
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;
}

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.

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;
}

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.

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.

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

Showing the parts of the HTML that are relevant to the :host pseudo-element.

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

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;
}

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.

]]>
https://css-tricks.com/web-component-pseudo-classes-and-pseudo-elements/feed/ 4 363929
Trailing Slashes on URLs: Contentious or Settled? https://css-tricks.com/trailing-slashes-on-urls-contentious-or-settled/ https://css-tricks.com/trailing-slashes-on-urls-contentious-or-settled/#comments Fri, 25 Feb 2022 21:13:08 +0000 https://css-tricks.com/?p=364070 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 …


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

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

]]>
https://css-tricks.com/trailing-slashes-on-urls-contentious-or-settled/feed/ 6 364070