Chris’ Corner: Scroll Driven Delight

Category Image 052

I’m pretty hot on Scroll-Driven Animations! What a wonderful idea that we can tie @keyframe animations timelines to scroll positions. And I’m sure the creators of it thought long and hard, because the API makes a ton of things possible. It’s not just “how far the entire page has scrolled”, although that’s possible. The progress through the animation can be tethered either to the scroll position of any element or to the position of an element within a scrollable container. Those are referred to as the Scroll Progress timeline or the View Progress timeline respectively. Slow clap, people.

Bramus Van Damme makes that nicely clear in this overview article. Bramus has been following, working on, creating demos, and writing about this stuff for a long time, and it was a smart move to wrap all that stuff up in a dedicated website for it.

I’m also a big fan of his co-worker Adam’s approach to an intro article here. Adam makes demos that are a smidge more designery and those tend to land with me. And speaking of designery demos, Ryan Mulligan’s beginning explorations are wonderful. He’s got some Polaroid photo style images that “blur in” when they scroll into view and a pair of photos that shuffle themselves as you scroll. I share Ryan’s sentiment that the tools Bramus has built are nearly crucial in understanding this stuff, since all the different keywords and values have such big effects.

These scroll-driven animations tend to be things that are just fun and could easily be thought of as progressive enhancement. So the fact that this is Chrome-only for now isn’t terribly bothersome to me, although it is polyfillable. We also didn’t get scroll-driven animations on the Interop 2024 list, but that doesn’t mean we won’t get Safari or Firefox support this year. Still could happen, just not really a guarantee.

Why do I think this is so cool? It’s not like we absolutely couldn’t do this before. Greensock has a ScrollTrigger plugin that is widely loved and has a pretty sweet API. (Here’s a great Collection.) I don’t think it’s terribly egregious to use JavaScript for these kind of effects, particularly if it makes them more maintainable, performant, or do things impossible any other way. But that’s the thing — when these abilities come back to native web technology like CSS, chances are the performance is going to be great and arguably more maintainable thinking long term as the people familiar with the technology will grow.

Yuriko Hirota did a great job of proving how much more performant using CSS for these types of animations are. The single-threaded nature of DOM interactive JavaScript means that if JavaScript is busy doing anything else, a JavaScript-powered animation is going to suffer from jankiness, that is, jerky and non-smooth animation. Even when JavaScript is quite busy, a CSS powered animation is fine. Those “scroll progress animations” are the classic demo of this web tech. Michelle Barker went deep on those this past year, starting with the basics and getting lovably weird as the article goes on.

Let’s end with a little tip! Bramus mentioned that if you’re setting up a scroll-driven animation that involves a target element and different scrolling element, if it’s not working, there is a good chance…

The culprit: an overflow: hidden sitting somewhere in between the target and the scroller.

It’s always the overflow, isn’t it? I find overflow is usually the culprit in figuring out why a sticky positioned item isn’t working as well. The solution, if you do actually need to deal with hiding overflow, is to use overflow: clip; instead, a relatively new ability. Kevin Powell covered a couple other scenarios where overflow: clip; saves the day, so it’s definitely worth knowing about!

I’ve been playing with Scroll-Driven Animations myself a bit. I wrote one bit about highlighting a bit of text as you scroll down a blog post, something I was inspired by from Lene Saile’s blog. As a response to a reader question, I also figured out how to zoom in images when they come into the viewport as well. Both of those ultimately use the scroll position to control the point in the animation to be at, which I think is usually nice, but I also enjoyed the idea that you can un-tether those things (say, run a 3s animation once an element becomes fully visible) by flipping a --custom-property in a keyframe which triggers a different keyframe, like Ryan Mulligan digs into.

It’s still early days for Scroll-Driven Animations and there are sure to be extremely clever ideas people will find for years. My jaw was already dropped by using them to fit text exactly to a container and to write conditional logic detecting if an element can scroll or not.

How To Draw Radar Charts In Web

Category Image 052

I got to work with a new type of chart for data visualization called a radar chart when a project asked for it. It was new to me, but the idea is that there is a circular, two-dimensional circle with plots going around the chart. Rather than simple X and Y axes, each plot on a radar chart is its own axis, marking a spot between the outer edge of the circle and the very center of it. The plots represent some sort of category, and when connecting them together, they are like vertices that form shapes to help see the relationship of category values, not totally unlike the vectors in an SVG.

Sometimes, the radar chart is called a spider chart, and it’s easy to see why. The axes that flow outward intersect with the connected plots and form a web-like appearance. So, if your Spidey senses were tingling at first glance, you know why.

You already know where we’re going with this: We’re going to build a radar chart together! We’ll work from scratch with nothing but HTML, CSS, and JavaScript. But before we go there, it’s worth noting a couple of things about radar charts.

First, you don’t have to build them from scratch. Chart.js and D3.js are readily available with convenient approaches that greatly simplify the process. Seeing as I needed just one chart for the project, I decided against using a library and took on the challenge of making it myself. I learned something new, and hopefully, you do as well!

Second, there are caveats to using radar charts for data visualization. While they are indeed effective, they can also be difficult to read when multiple series stack up. The relationships between plots are not nearly as decipherable as, say, bar charts. The order of the categories around the circle affects the overall shape, and the scale between series has to be consistent for drawing conclusions.

That all said, let’s dive in and get our hands sticky with data plots.

The Components

The thing I like immediately about radar charts is that they are inherently geometrical. Connecting plots produces a series of angles that form polygon shapes. The sides are straight lines. And CSS is absolutely wonderful for working with polygons given that we have the CSS polygon() function for drawing them by declaring as many points as we need in the function’s arguments.

We will start with a pentagonal-shaped chart with five data categories.

See the Pen Radar chart (Pentagon) [forked] by Preethi Sam.

There are three components we need to establish in HTML before we work on styling. Those would be:

  1. Grids: These provide the axes over which the diagrams are drawn. It’s the spider web of the bunch.
  2. Graphs: These are the polygons we draw with the coordinates of each data plot before coloring them in.
  3. Labels: The text that identifies the categories along the graphs’ axes.

Here’s how I decided to stub that out in HTML:

<!-- GRIDS -->
<div class="wrapper">
  <div class="grids polygons">
    <div></div>
  </div>
  <div class="grids polygons">
    <div></div>
  </div>
  <div class="grids polygons">
    <div></div>
  </div>
</div>

<!-- GRAPHS -->
<div class="wrapper">
  <div class="graphs polygons">
    <div><!-- Set 1 --></div>
  </div>
  <div class="graphs polygons">
    <div><!-- Set 2 --></div>
  </div>
  <div class="graphs polygons">
    <div><!-- Set 3 --></div>
  </div>
  <!-- etc. -->
</div>

<!-- LABELS -->
<div class="wrapper">
  <div class="labels">Data A</div>
  <div class="labels">Data B</div>
  <div class="labels">Data C</div>
  <div class="labels">Data D</div>
  <div class="labels">Data E</div>
  <!-- etc. -->
</div>

I’m sure you can read the markup and see what’s going on, but we’ve got three parent elements (.wrapper) that each holds one of the main components. The first parent contains the .grids, the second parent contains the .graphs, and the third parent contains the .labels.

Base Styles

We’ll start by setting up a few color variables we can use to fill things in as we go:

:root {
  --color1: rgba(78, 36, 221, 0.6); /* graph set 1 */
  --color2: rgba(236, 19, 154, 0.6); /* graph set 2 */
  --color3: rgba(156, 4, 223, 0.6); /* graph set 3 */
  --colorS: rgba(255, 0, 95, 0.1); /* graph shadow */
}

Our next order of business is to establish the layout. CSS Grid is a solid approach for this because we can place all three grid items together on the grid in just a couple of lines:

/* Parent container */
.wrapper { display: grid; }

/* Placing elements on the grid */
.wrapper > div {
  grid-area: 1 / 1; /* There's only one grid area to cover */
}

Let’s go ahead and set a size on the grid items. I’m using a fixed length value of 300px, but you can use any value you need and variablize it if you plan on using it in other places. And rather than declaring an explicit height, let’s put the burden of calculating a height on CSS using aspect-ratio to form perfect squares.

/* Placing elements on the grid */
.wrapper div {
  aspect-ratio: 1 / 1;
  grid-area: 1 / 1;
  width: 300px;
}

We can’t see anything just yet. We’ll need to color things in:

/* ----------
Graphs
---------- */
.graphs:nth-of-type(1) > div { background: var(--color1); }
.graphs:nth-of-type(2) > div { background: var(--color2); }
.graphs:nth-of-type(3) > div { background: var(--color3); }

.graphs {
  filter: 
    drop-shadow(1px 1px 10px var(--colorS))
    drop-shadow(-1px -1px 10px var(--colorS))
    drop-shadow(-1px 1px 10px var(--colorS))
    drop-shadow(1px -1px 10px var(--colorS));
}

/* --------------
Grids 
-------------- */
.grids {
  filter: 
    drop-shadow(1px 1px 1px #ddd)
    drop-shadow(-1px -1px 1px #ddd)
    drop-shadow(-1px 1px 1px #ddd)
    drop-shadow(1px -1px 1px #ddd);
    mix-blend-mode: multiply;
}

.grids > div { background: white; }

Oh, wait! We need to set widths on the grids and polygons for them to take shape:

.grids:nth-of-type(2) { width: 66%; }
.grids:nth-of-type(3) { width: 33%; }

/* --------------
Polygons 
-------------- */
.polygons { place-self: center; }
.polygons > div { width: 100%; }

Since we’re already here, I’m going to position the labels a smidge and give them width:

/* --------------
Labels
-------------- */
.labels:first-of-type { inset-block-sptart: -10%; }

.labels {
  height: 1lh;
  position: relative;
  width: max-content;
}

We still can’t see what’s going on, but we can if we temporarily draw borders around elements.

See the Pen Radar chart layout [forked] by Preethi Sam.

All combined, it doesn’t look all that great so far. Basically, we have a series of overlapping grids followed by perfectly square graphs stacked right on top of one another. The labels are off in the corner as well. We haven’t drawn anything yet, so this doesn’t bother me for now because we have the HTML elements we need, and CSS is technically establishing a layout that should come together as we start plotting points and drawing polygons.

More specifically:

  • The .wrapper elements are displayed as CSS Grid containers.
  • The direct children of the .wrapper elements are divs placed in the exact same grid-area. This is causing them to stack one right on top of the other.
  • The .polygons are centered (place-self: center).
  • The child divs in the .polygons take up the full width (width:100%).
  • Every single div is 300px wide and squared off with a one-to-one aspect-ratio.
  • We’re explicitly declaring a relative position on the .labels. This way, they can be automatically positioned when we start working in JavaScript.

The rest? Simply apply some colors as backgrounds and drop shadows.

Calculating Plot Coordinates

Don’t worry. We are not getting into a deep dive about polygon geometry. Instead, let’s take a quick look at the equations we’re using to calculate the coordinates of each polygon’s vertices. You don’t have to know these equations to use the code we’re going to write, but it never hurts to peek under the hood to see how it comes together.

x1 = x + cosθ1 = cosθ1 if x=0
y1 = y + sinθ1 = sinθ1 if y=0
x2 = x + cosθ2 = cosθ2 if x=0
y2 = y + sinθ2 = sinθ2 if y=0
etc.

x, y = center of the polygon (assigned (0, 0) in our examples)

x1, x2… = x coordinates of each vertex (vertex 1, 2, and so on)
y1, y2… = y coordinates of each vertex
θ1, θ2… = angle each vertex makes to the x-axis

We can assume that 𝜃 is 90deg (i.e., 𝜋/2) since a vertex can always be placed right above or below the center (i.e., Data A in this example). The rest of the angles can be calculated like this:

n = number of sides of the polygon

𝜃1 = 𝜃0 + 2𝜋/𝑛 = 𝜋/2 + 2𝜋/𝑛
𝜃2 = 𝜃0 + 4𝜋/𝑛 = 𝜋/2 + 4𝜋/𝑛
𝜃3 = 𝜃0 + 6𝜋/𝑛 = 𝜋/2 + 6𝜋/𝑛
𝜃3 = 𝜃0 + 8𝜋/𝑛 = 𝜋/2 + 8𝜋/𝑛
𝜃3 = 𝜃0 + 10𝜋/𝑛 = 𝜋/2 + 10𝜋/𝑛

Armed with this context, we can solve for our x and y values:

x1 = cos(𝜋/2 + 2𝜋/# sides)
y1 = sin(𝜋/2 + 2𝜋/# sides)
x2 = cos(𝜋/2 + 4𝜋/# sides)
y2 = sin(𝜋/2 + 4𝜋/# sides)
etc.

The number of sides depends on the number of plots we need. We said up-front that this is a pentagonal shape, so we’re working with five sides in this particular example.

x1 = cos(𝜋/2 + 2𝜋/5)
y1 = sin(𝜋/2 + 2𝜋/5)
x2 = cos(𝜋/2 + 4𝜋/5)
y2 = sin(𝜋/2 + 4𝜋/5)
etc.
Drawing Polygons With JavaScript

Now that the math is accounted for, we have what we need to start working in JavaScript for the sake of plotting the coordinates, connecting them together, and painting in the resulting polygons.

For simplicity’s sake, we will leave the Canvas API out of this and instead use regular HTML elements to draw the chart. You can, however, use the math outlined above and the following logic as the foundation for drawing polygons in whichever language, framework, or API you prefer.

OK, so we have three types of components to work on: grids, graphs, and labels. We start with the grid and work up from there. In each case, I’ll simply drop in the code and explain what’s happening.

Drawing The Grid

// Variables
let sides = 5; // # of data points
let units = 1; // # of graphs + 1
let vertices = (new Array(units)).fill(""); 
let percents = new Array(units);
percents[0] = (new Array(sides)).fill(100); // for the polygon's grid component
let gradient = "conic-gradient(";
let angle = 360/sides;

// Calculate vertices
with(Math) { 
  for(i=0, n = 2 * PI; i < sides; i++, n += 2 * PI) {
    for(j=0; j < units; j++) {
      let x = ( round(cos(-1 * PI/2 + n/sides) * percents[j][i]) + 100 ) / 2; 
      let y = ( round(sin(-1 * PI/2 + n/sides) * percents[j][i]) + 100 ) / 2; 
      vertices[j] += ${x}% ${y} ${i == sides - 1 ? '%':'%, '};
  }
  gradient += white ${
    (angle &#42; (i+1)) - 1}deg,
    #ddd ${ (angle &#42; (i+1)) - 1 }deg,
    #ddd ${ (angle &#42; (i+1)) + 1 }deg,
    white ${ (angle &#42; (i+1)) + 1 }deg,;}
}

// Draw the grids
document.querySelectorAll('.grids>div').forEach((grid,i) => {
  grid.style.clipPath =polygon(${ vertices[0] });
});
document.querySelector('.grids:nth-of-type(1) > div').style.background =${gradient.slice(0, -1)} );

Check it out! We already have a spider web.

See the Pen Radar chart (Grid) [forked] by Preethi Sam.

Here’s what’s happening in the code:

  1. sides is the number of sides of the chart. Again, we’re working with five sides.
  2. vertices is an array that stores the coordinates of each vertex.
  3. Since we are not constructing any graphs yet — only the grid — the number of units is set to 1, and only one item is added to the percents array at percents[0]. For grid polygons, the data values are 100.
  4. gradient is a string to construct the conic-gradient() that establishes the grid lines.
  5. angle is a calculation of 360deg divided by the total number of sides.

From there, we calculate the vertices:

  1. i is an iterator that cycles through the total number of sides (i.e., 5).
  2. j is an iterator that cycles through the total number of units (i.e., 1).
  3. n is a counter that counts in increments of 2*PI (i.e., 2𝜋, 4𝜋, 6𝜋, and so on).

The x and y values of each vertex are calculated as follows, based on the geometric equations we discussed earlier. Note that we multiply 𝜋 by -1 to steer the rotation.

cos(-1 * PI/2 + n/sides) // cos(𝜋/2 + 2𝜋/sides), cos(𝜋/2 + 4𝜋/sides)...
sin(-1 * PI/2 + n/sides) // sin(𝜋/2 + 2𝜋/sides), sin(𝜋/2 + 4𝜋/sides)...

We convert the x and y values into percentages (since that is how the data points are formatted) and then place them on the chart.

let x = (round(cos(-1 * PI/2 + n/sides) * percents[j][i]) + 100) / 2;
let y = (round(sin(-1 * PI/2 + n/sides) * percents[j][i]) + 100) / 2;

We also construct the conic-gradient(), which is part of the grid. Each color stop corresponds to each vertex’s angle — at each of the angle increments, a grey (#ddd) line is drawn.

gradient += 
  `white ${ (angle * (i+1)) - 1 }deg,
   #ddd ${ (angle * (i+1)) - 1 }deg,
   #ddd ${ (angle * (i+1)) + 1 }deg,
   white ${ (angle * (i+1)) + 1 }deg,`

If we print out the computed variables after the for loop, these will be the results for the grid’s vertices and gradient:

console.log(polygon( ${vertices[0]} )); /* grid’s polygon */
// polygon(97.5% 34.5%, 79.5% 90.5%, 20.5% 90.5%, 2.5% 34.5%, 50% 0%)

console.log(gradient.slice(0, -1)); /* grid’s gradient */
// conic-gradient(white 71deg, #ddd 71deg,# ddd 73deg, white 73deg, white 143deg, #ddd 143deg, #ddd 145deg, white 145deg, white 215deg, #ddd 215deg, #ddd 217deg, white 217deg, white 287deg, #ddd 287deg, #ddd 289deg, white 289deg, white 359deg, #ddd 359deg, #ddd 361deg, white 361deg

These values are assigned to the grid’s clipPath and background, respectively, and thus the grid appears on the page.

The Graph

// Following the other variable declarations 
// Each graph's data points in the order [B, C, D... A] 
percents[1] = [100, 50, 60, 50, 90]; 
percents[2] = [100, 80, 30, 90, 40];
percents[3] = [100, 10, 60, 60, 80];

// Next to drawing grids
document.querySelectorAll('.graphs > div').forEach((graph,i) => {
  graph.style.clipPath =polygon( ${vertices[i+1]} );
});

See the Pen Radar chart (Graph) [forked] by Preethi Sam.

Now it looks like we’re getting somewhere! For each graph, we add its set of data points to the percents array after incrementing the value of units to match the number of graphs. And that’s all we need to draw graphs on the chart. Let’s turn our attention to the labels for the moment.

The Labels

// Positioning labels

// First label is always set in the top middle
let firstLabel = document.querySelector('.labels:first-of-type');
firstLabel.style.insetInlineStart =calc(50% - ${firstLabel.offsetWidth / 2}px);

// Setting labels for the rest of the vertices (data points). 
let v = Array.from(vertices[0].split(' ').splice(0, (2 * sides) - 2), (n)=> parseInt(n)); 

document.querySelectorAll('.labels:not(:first-of-type)').forEach((label, i) => {
  let width = label.offsetWidth / 2; 
  let height = label.offsetHeight;
  label.style.insetInlineStart = calc( ${ v[i&#42;2] }% + ${ v[i&#42;2] &lt; 50 ? - 3&#42;width : v[i&#42;2] == 50 ? - width: width}px );
  label.style.insetBlockStart = calc( ${ v[(i&#42;2) + 1] }% - ${ v[(i &#42; 2) + 1] == 100 ? - height: height / 2 }px );
});

The positioning of the labels is determined by three things:

  1. The coordinates of the vertices (i.e., data points) they should be next to,
  2. The width and height of their text, and
  3. Any blank space needed around the labels so they don’t overlap the chart.

All the labels are positioned relative in CSS. By adding the inset-inline-start and inset-block-start values in the script, we can reposition the labels using the values as coordinates. The first label is always set to the top-middle position. The coordinates for the rest of the labels are the same as their respective vertices, plus an offset. The offset is determined like this:

  1. x-axis/horizontal
    If the label is at the left (i.e., x is less than 50%), then it’s moved towards the left based on its width. Otherwise, it’s moved towards the right side. As such, the right or left edges of the labels, depending on which side of the chart they are on, are uniformly aligned to their vertices.
  2. y-axis/vertical
    The height of each label is fixed. There’s not much offset to add except maybe moving them down half their height. Any label at the bottom (i.e., when y is 100%), however, could use additional space above it for breathing room.

And guess what…

We’re Done!

See the Pen Radar chart (Pentagon) [forked] by Preethi Sam.

Not too shabby, right? The most complicated part, I think, is the math. But since we have that figured out, we can practically plug it into any other situation where a radar chart is needed. Need a four-point chart instead? Update the number of vertices in the script and account for fewer elements in the markup and styles.

In fact, here are two more examples showing different configurations. In each case, I’m merely increasing or decreasing the number of vertices, which the script uses to produce different sets of coordinates that help position points along the grid.

Need just three sides? All that means is two fewer coordinate sets:

See the Pen Radar chart (Triangle) [forked] by Preethi Sam.

Need seven sides? We’ll produce more coordinate sets instead:

See the Pen Radar chart (Heptagon) [forked] by Preethi Sam.

Chris’ Corner: More Like Celebrating Style Skills

Category Image 052

It’s January and we’re seeing a little round of CSS wishlists make the rounds.

  • Tyler Sticka is mostly hoping for better support on stuff that already is starting to cook, like View Transitions, anchor positioning, balanced text, and scroll-driven animations. But he’s got his own new wishes and has done the leg-work to properly propose them, like more useful vertical alignment (without extra work).
  • Christopher Kirk-Nielsen also has some wishes for better support for thing that ought to be doing that naturally, like Style Queries, but also has some really interesting ideas of his own like using transform to force an element of an unknown size into a specific size, which he also wrote up properly.
  • Manuel Matuzović has more love for Style (and State) Queries (agreed there is some real potential here to reduce CSS size and complexity) and @scope. On the doesn’t-exist-yet side, a vote for mixins to which I’ll echo: Yes, please!
  • Elly Loel weighted in last year using the same distinctions: stuff that is already cooking (e.g. Custom Media) and stuff that doesn’t exist yet (e.g. detecting flex-wrap). Good news for Elly, some stuff from the list got done, like subgrid.
  • Nathan Knowler joins the choir with strong votes for Style Queries, Scroll-Driven Animations, and @scope, among others.
  • Sarah Gebauer has a great one on her wishlist about borders and controlling the offset. Hey, SVG can do it and it’s super cool. Plus a shout out to a personal favorite: more useful attributes and the attr() function.

Phew! That’s a lot of people taking the time to be pumped about CSS and make clear what they want. The way CSS is going lately I would not be surprised to see massive progress again in 2024.


Stephanie Eckles points out 12 one-liners in CSS that are all tremendously useful and powerful.

One liners.

Stuff like p { text-wrap: pretty; } that just makes web type all the nicer. Five years ago our gapping mouths would have been on the floor if they knew what was coming.


Brecht De Ruyte took two brand new CSS and HTML things and smashed them together:

  • The new <selectmenu> element, which is exactly like <select> except fully CSS styleable.
  • The Anchor Position API

It’s a cool interaction that is supposed to end up like this:

But this was actually published mid-last-year, and the demo is already broken even. It only ever worked in Chrome Canary with the Experimental Web Platform Features flag turned on, so that just goes to show you how fast this experimental stuff moves.

It’s not <selectmenu> any more, it’s <selectlist>, but even after forking the demo and trying to fix it, I couldn’t get it. I suspect it’s changes to the Anchor Positioning API that I’m not up to snuff on. I do suspect this interaction is possible today, but you’d either be using cutting edge APIs that may or may not be polyfillable, and/or recreating a ton of interaction and accessibility stuff that you’d get for free with Brecht’s implementation.


Ben Frain has a very clever idea for drawing angled lines in CSS (like a line chart). Which, weirdly, isn’t a particularly easy thing to do in CSS. You can make lines various ways and rotate them and stuff, but it’s harder to be like: draw a line from here to here. SVG is typically the path for that.

In Ben’s technique, you take advantage of the polygon() function and clip-path. You “draw” points in the clip path at certain coordinates, then go back over the exact same coordinates with a 1px difference, leaving that little gap where a new color can “shine through”.

Like:

clip-path: polygon(
  0% 60%,
  20% 90%,
  40% 43.33%,
  60% 61.67%,
  80% 23.33%,
  100% 18.33%,
  100% calc(18.33% - 1px),
  80% calc(23.33% - 1px),
  60% calc(61.67% - 1px),
  40% calc(43.33% - 1px),
  20% calc(90% - 1px),
  0% calc(60% - 1px)
);
Demo

Robin Rendle once made a bunch of different chart types in CSS only, but I don’t think I’ve seen a CSS “sparkline” like this until now.

Unraveling the Wonders of Bluetooth: Connecting the World Wirelessly

Category Image 052

Few technologies have had as deep an influence as Bluetooth in a world where continuous connection has become a fundamental part of our everyday lives. Bluetooth has quietly revolutionized the way we connect and interact wirelessly, from our headphones to our automobiles, from medical equipment to smart home products.

Evolution of Bluetooth: Pioneering Wireless Connectivity

The inception of Bluetooth traces back to the late 1990s, emerging from the collaborative efforts of engineers at Ericsson, a telecommunications company based in Sweden. The name “Bluetooth” itself is a nod to Harald Bluetooth, a Danish king known for uniting disparate regions—an apt metaphor for a technology designed to unite disparate devices.

The Complex But Awesome CSS border-image Property

Category Image 052

The border-image property is nothing new. Even deprecated Internet Explorer supports it, so you know we’re treading well-charted territory. At the same time, it’s not exactly one of those properties you likely keep close at hand, and its confusing concepts of “slicing” and “outsets” don’t make it the easiest property to use.

I’m here to tell you that border-image is not only capable of producing some incredibly eye-catching UI designs but is also able to accomplish some other common patterns we often turn to other properties and approaches for.

In this article, my plan is to dispel the confusing Aspects of border-image without getting into a bunch of theoretical explanations and technical jargon. Instead, we’re going to have fun with the property, using it to create shapes and put a different spin on things like range sliders, tooltips, and title decorations.

By the end of this, I hope that border-image becomes your new favorite property just as it has become mine!

The Concept of Image Borders

There are a few specific Aspects of border-image that I think are crucial for understanding how it works.

It’s Easy To Accidentally Override A Border Image

The CSS Backgrounds and Border Module Level 3 specification says border-image should replace any regular border you define, but it’s not always the case. Try the code below, and all you will see is a red border.

/* All I see is a red border */
.element {
  border-image: linear-gradient(blue, red) 1;
  border: 5px solid red;
}

That’s because we’re technically declaring border after border-image. Order really does matter when working with border-image!

/* 👍 */
.element {
  border: 5px solid red;
  border-image: linear-gradient(blue, red) 1;
}

You can already see how this could be confusing for anyone jumping into border-image, especially for the first time. You will also notice that our gradient border has a thickness equal to 5px, which is the border-width we defined.

I make it a personal habit not to use border and border-image together because it helps me avoid overriding the border image I’m trying to create and be able to control the border decoration using only one property (even if both can be used together). So, if you get a strange result, don’t forget to check if you have a border declared somewhere.

It Is Painted Over Backgrounds And Box Shadows

The second tip I want to offer is that border-image is painted above the element’s background and box-shadow but below the element’s content. This detail is important for some of the tricks we will use in later examples. The following Pen demonstrates how a border image is applied in that order:

If we were to translate the figure above into code using the provided variables as values, it would look like this:

border-image:
  linear-gradient(...)
  s-top s-right s-bottom s-left / 
  w-top w-right w-bottom w-left /
  o-top o-right o-bottom o-left;

By default, border-image considers the boundaries of the element (illustrated with the blue dotted border in Figure 1) as its area to paint the gradient, but we can change this using the <outset> to increase that area and create an overflow. This is super useful to have “outside” decorations.

Then, the <width> is used to split the area into nine regions, and the <slice> is used to split the source (i.e., the gradient) into nine slices as well. From there, we assign each slice to its corresponding region. Each slice is stretched to fill its assigned region, and if they don’t share equal dimensions, the result is typically a distorted image slice. Later on, we will learn how to control that and prevent distortion.

The middle region is kept empty by default. That said, it is totally possible to use the fill keyword to do what it says and fill the middle region with slice nine (which is always the center slice).

border-image: linear-gradient(...) fill
  s-top s-right s-bottom s-left / 
  w-top w-right w-bottom w-left /
  o-top o-right o-bottom o-left;

I know this was a pretty fast primer on border-image, but I think it’s all we need to do some pretty awesome stuff. Let’s jump into the fun and start experimenting with effects.

Gradient Overlay

Our first experiment is to add a gradient overlay above an existing background. This is a fairly common pattern to improve the legibility of text by increasing the contrast between the text color and the background color.

There are several well-known approaches to setting an overlay between text and content. Here’s one from Chris Coyier back in 2013. And that isn’t even the most widely-used approach, which is likely using pseudo-elements.

But border-image gives us a one-line way to pull it off:

.overlay {
  border-image: fill 0 linear-gradient(#0003,#000); 
}

That’s all! No extra element, no pseudo-element, and no need to modify the background property.

Well, guess what? The border-image property can pull it off with one line of code:

.full-background {
  border-image: conic-gradient(pink 0 0) fill 0//0 100vw;
}

If you compare what we just did with the gradient overlay example, the <outset> is the only difference between the implementations. Other than that, we have a single slice placed in the middle region that covers the entire area we extended to the edge of the screen.

We are not limited to a solid color, of course, since we are working with gradients.

Fancy Headings

Another thing we can use border-image for is decorating headings with fancy borders. Let’s start with the exact same implementation we used for the full-width backgrounds. Only this time, we’re replacing the conic-gradient() with a linear-gradient():

.full-background {
  border-image: linear-gradient(0deg, #1095c1 5px, lightblue 0) fill 0//0 100vw;
}

Now we apply this to an <h1> element:

So, that’s two different ways to get the same effect using the same border-image syntax. We can actually get this a third way as well:

.full-line {
  border-image: conic-gradient(#1095c1 0 0) 0 0 1 0/0 0 8px 0/0 100vw 0 0;
}

This time, I have defined a bottom slice equal to 1 (unitless values are computed as pixels), which produces two slices, the seventh (bottom center) and the ninth (center). From there, I have set the seventh region to a height of 8px. Note that I am not using the fill keyword this time, so the middle region is not filled like it was last time. Instead, we only fill the seventh region that takes up 100% of the boder-image’s area and 8px of its height.

You’re wondering why I am defining a slice equal to 1, right? The goal is to have only two slices: the seventh (bottom center) and the ninth (middle), and since we are applying a solid color, the size doesn't matter. That’s why I used 1; a small positive value. Any value will work (e.g., 0.5, 2, 100, 50%, 76%, and so on); it’s just that 1 is shorter to type. Remember that the slice will get stretched within its region, so 1px is enough to fill the whole region.

Here’s the deal: The slice value doesn’t really matter when working with a solid coloration. In most cases, the value winds up being 0 (empty) or 1 (filled). You can think of it as binary logic.

We could do this a fourth way!

.full-line {
  border-image: conic-gradient(#1095c1 0 0) 0 1 0 0/calc(100% - 8px) 100% 0 0/0 100vw 0 0;
}

I’ll let you do the work to figure out how the above CSS works. It’s a good opportunity to get a feel for slicing elements. Take a pen and paper and try to identify which slices we are using and which regions will be filled.

One thing that makes border-image a complex property is all the different ways to achieve the same result. You can wind up with a lot of different combinations, and when all of them produce the same result, it’s tough to form a mental model for understanding how all of the values work together.

Even though there is no single “right” way to do these heading borders, I prefer the second syntax because it allows me to simply change one color value to establish a “real” gradient instead of a solid color.

.full-line {
  border-image: repeating-linear-gradient(...) fill 0 /
    calc(100% - var(--b)) 0 0/0 100vw 0 0 repeat;
}

Let’s try another syntax for the same effect:

h2 {
  --s: 3px;   /* the thickness */
  --w: 100px; /* the width */
  --g: 10px;  /* the gap */
  border-image: 
     conic-gradient(red 0 0) 
     0 50%/calc(50% - var(--s)/2) var(--w)/0 calc(var(--w) + var(--g));
}

The top and bottom values of the <slice> are equal to 0, and the left and right ones are equal to 50%. This means that slices six and eight share the gradient. All the other slices — including the center — are empty.

As far as the regions go, the top and bottom regions (consisting of regions 1, 5, and 2 at the top and regions 4, 7, and 3 at the bottom) have a height equal to 50% - var(--s)/2 leaving the --s variable as a height for the remaining regions (6, 8, and 9). The right and the left regions have a width equal to the --w variable. Since slices 6 and 8 are the only ones that are filled, the only regions we need to care about are 6 and 8. Both have a height equal to the border’s thickness, --s, and a width equal to --w.

I think you know how the rest of the story goes.

Notice I am using 50% as a slice. It demonstrated how any value does the job, as we discussed in the last section when I explained why I chose to use a value of 1 but also to prepare for the next effect where I will be using a real gradient:

See the Pen Horizontal lines around your title with gradient coloration by Temani Afif.

When it comes to real gradients, the value of the slice is important, and sometimes you need very precise values. To be honest, this can be very tricky, and I even get lost trying to figure out the right value.

Let’s end this section with more examples of title decorations. When combined with other properties, border-image can make really nice effects.

See the Pen Fancy title divider with one element by Temani Afif.

See the Pen Fancy title divider with one element by Temani Afif.

More Examples

Now that we’ve seen several detailed examples of how border-image, I’m going to drop in several other examples. Rather than explaining them in great detail, try to explain them in your own words by inspecting the CSS, and use these as inspiration for your own work.

Infinite Image Decorations

When it comes to images, border-image can be a lifesaver since we don’t have access to pseudo-elements. Here are some cool infinite decorations where we can have a touch of 3D effect.

See the Pen Infinite image shadow by Temani Afif.

See the Pen Infinite image shadow II by Temani Afif.

See the Pen Infinite image stripes shadow by Temani Afif.

See the Pen 3D trailing shadow for images by Temani Afif.

If you check the code in these examples, you will find they share nearly the same structure. If you have trouble recognizing the pattern, please don’t hesitate to leave a comment at the end of this article, and I would be happy to point it out.

Custom Range Slider

I wrote a detailed article on how to create the following example, and you can refer to it for range slider variations using the same technique.

See the Pen CSS only custom range sliders by Temani Afif.

I used border-image and styled only the “thumb” element. Range inputs are known to have different implementation cross-browser, but the “thumb” is common between all of them.

Ribbon Shapes

In case you missed it, I have created a collection of more than 100 single-element ribbon shapes, and some of them rely on border-image. I call them the “infinite ribbons.”

See the Pen Full screen Ribbon title by Temani Afif.

See the Pen Infinite Ribbon Shapes by Temani Afif.

Heart Shapes

I have written about CSS heart shapes using different approaches, and one of them uses a border-image technique.

.heart {
  width: 200px;
  aspect-ratio: 1;
  border-image: radial-gradient(red 69%,#0000 70%) 84.5%/50%;
  clip-path: polygon(-42% 0,50% 91%, 142% 0);
}

See the Pen Heart shape using border-image by Temani Afif.

The interesting part here is the slice that is equal to 84.5%. That is a bigger value than 50%, so it may seem incorrect since the total exceeds 100%. But it’s perfectly fine because slices are able to overlap one another!

When using values bigger than 50%, the corner slices (1, 2, 3, and 4) will share common parts, but the other slices are considered empty. Logically, when using a slice equal to 100%, we will end with four slices containing the full source.

Here is an example to illustrate the trick:

See the Pen Overview of the slice effect by Temani Afif.

The slider will update the slice from 0% to 100%. On the left, you can see how the corner slices (1-4) grow. Between 0% and 50%, the result is logical and intuitive. Bigger than 50%, you start having the overlap. When reaching 100%, you can see the full circle repeated four times because each slice contains the full gradient, thanks to the overlap.

It can be confusing and not easy to visualize, but overlaps can be really useful to create custom shapes and fancy decorations.

Tooltips

What about a simple tooltip shape with only two properties? Yes, it’s possible!

See the Pen A simple Tooltip using 2 CSS properties by Temani Afif.

.tooltip {
  /* triangle dimension */
  --b: 2em; /* base */
  --h: 1em; /* height*/

  border-image: conic-gradient(#CC333F 0 0) fill 0//var(--h);
  clip-path: 
    polygon(0 100%,0 0,100% 0,100% 100%,
      calc(50% + var(--b)/2) 100%,
      50% calc(100% + var(--h)),
      calc(50% - var(--b)/2) 100%);
}
Filling Border Radius

Unlike most decorative border properties (e.g., box-shadow, outline, border, and so on), border-image doesn’t respect border-radius. The element is still a box, even if we’ve rounded off the corners. Other properties will recognize the visual boundary established by border-radius, but border-image bleeds right through it.

That could be a drawback in some instances, I suppose, but it’s also one of the quirky things about CSS that can be leveraged for other uses like creating images with inner radius:

See the Pen Inner radius to image element by Temani Afif.

Cool, right? Only one line of code makes it happen:

img {
  --c: #A7DBD8;
  --s: 10px; /* the border thickness*/

  border-image: conic-gradient(var(--c) 0 0) fill 0 // var(--s);
}

We can even leave the center empty to get a variation that simply borders the entire element:

See the Pen Rounded images inside squares by Temani Afif.

Conclusion

Did you know border-image property was such a powerful — and flexible — CSS property? Despite the challenge it takes to understand the syntax, there are ways to keep the code clean and simple. Plus, there is often more than one “right” way to get the same result. It’s a complicated and robust CSS feature.

If the concepts of slicing and defining regions with border-image are still giving you problems, don’t worry. That’s super common. It took me a lot of time to fully understand how border-image works and how to use it with different approaches to the syntax. Give yourself plenty of time, too. It helps to re-read things like this article more than once to let the concepts sink in.

Complexities aside, I hope that you will add border-image to your toolbox and create a lot of magic with it. We can do even more with border-image than what was demonstrated here. I actually experiment with this sort of stuff frequently and share my work over at my CSS Tip website. Consider subscribing (RSS) to keep up with the fun and weird things I try.

Special thanks to @SelenIT2, who pushed me to explore this property and wrote an excellent article on it.

Chris’ Corner: Switch

Category Image 052

The “switch” is a pretty common design pattern on the web. It’s pretty much a checkbox. In fact, under the HTML hood, it really ought to be an <input type="checkbox"> or perhaps a <select> with just two <option>s (or a third if there is an indeterminate state).

But unfortunately, the web doesn’t give us any primitive that looks particularly switch-like, as in, some kind of knob that is flipped one way or the other. So we use CSS. For example, we hide the checkbox one way or another, making sure there is still a discoverable clickable area, then with the :checked selector, style something that looks switch-like.

Here’s a very classic example.

Marcus Burnette nicely re-creating the iOS toggle look

I’m sure you could imagine using that for, say, toggling email notification settings on an off for some sort of app. We use them here on CodePen quite a bit, for stuff like toggling the privacy of a Pen. Or you might use a toggle to switch a site between dark mode and light mode.

Speaking of that, Aleksandr Hovhannisyan has a solid article about the struggles of a dark mode toggle. You’d think it would be pretty straightforward, but it really isn’t. Consider that users have system-level preferences in addition to your site-level preference, and you have to honor them in the proper order. Plus you have to set the controls properly as well as actually style the site accordingly, ideally without temporarily flashing the wrong colors. (FART). Aleksandr does a good job of it and links to other posts that have done a similarly good job. It’s always way more code than you want it to be, leading me to think browsers and standards could and should get more involved, but I also admit I don’t have a perfect idea on what they should do. Chrome has played with Auto Dark Mode, but it’s not clear how that trial went. (And speaking of Dark Mode, this gallery is pretty nicely done.)

Anyway, I was trying to talk about switches!

I saw Jen Simmons note that Safari is playing with a native switch. Here’s the HTML:

<input type="checkbox" switch>

Nice.

And here’s what it looks like by default:

No big surprise there! It’s the native iOS toggle come to life. It respects accent-color in CSS like other form controls, which is great. But better, it has really sensible pseudo elements you can grab and style. You get ::thumb and ::track elements (nice clear naming) plus ::before and ::after work on the element itself, so there are a lot of possibilities.

.custom-switch { }
.custom-switch::thumb { }
.custom-switch::track { }

.custom-switch:checked::thumb { }
.custom-switch:checked::track { }

.custom-switch::checked::after { }
.custom-switch::checked::before { }

Tim Nguyen has demos that do a good job of showing off the possibilities with clean readable CSS.

The best part of browsers providing this kind of thing for us, to me, is that now you don’t have to worry about dinking up the accessibility. Now, as long as you follow the normal HTML structure of a labelled checkbox in a form, you’re good. No worries about the way you hid the original checkbox screwing things up. You are taking visual control though, so do take care to make sure the checked and unchecked values are at least as clear as a checked or unchecked checkbox.

Chris’ Corner: Mentals and Models

Category Image 052

Josh Comeau has a new learning guide out for CSS grid: An Interactive Guide to CSS Grid. I like how Josh gets into the “mental model” right away, because that’s the biggest thing with big APIs like this: you need to get the basics of how it works in your head so you know when and why to reach for it. The niche details are less important. You can look them up. Mostly you need to understand that the thing exists and what it is capable of. Ideally you can write a basic setup from memory. Then the more niche stuff you reference. Josh’s guide, especially with the interactivity, loads you up with that mental model.

Josh has done the same before with an An Interactive Guide to Flexbox. The interactive bit goes a long way in locking in that mental model. In my CSS-Tricks days, our guide to flexbox was also decent at explaining the model I think, and also was a decent reference guide at the same time. But it didn’t have that interactivity so people were left to extracting the information to their own code to play. Once people are writing their own layout code, I’d wager that’s also a pretty big moment for locking in a mental model. CodePen editable embeds might be a good way to bring that to guides like this. I know that a very lot of people had their ah-ha moment with flexbox via Flexbox Froggy, and I think it’s that combination of interactivity and real code that made that so effective.


Speaking of Cool Josh Ideas, have you seen his operator lookup page? JavaScript has a pretty decent number of operators, and some of them you really have to look up to remember sometimes:

Sometimes my brain goes… what’s the one where you set the value, but if the value is null, provide a fallback. Which is this one.


Do you know about import maps yet? They aren’t some new niche thing, they are supported across the board. (Well, a little new, maybe).

I like Yoshi Huang’s article(s) on them, they do a good job of setting the stage.

My brain goes a little like this. This is really common code to see, but it’s actually invalid JavaScript:

import lodash from "lodash";

If you see code like that, traditionally, it means that the JavaScript will be going through a processor of some sort that is going to resolve "lodash" to some actual location where that code can be found. If we wanted that code to be valid JavaScript, we’d either have to:

  1. Import from a relative file path (e.g. that string has got to start with a dot or a slash)
  2. Import from a URL

The latter being like:

import lodash from 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm'

But now, the original import statement actually is valid. That is, as long as we use an import map. From Yoshi’s article, something like:

<script type="importmap">
{
  "imports": {
     "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

Think of it like string replacement. The "lodash" in the import statement becomes "/node_modules/lodash-es/lodash.js" and a valid relative file path import. That could be a URL as well.

I like the idea that an import map can help orchestrate imports across an entire codebase from one place, and eliminate a lot of repetition, and be a tool in using less build processes.

I talk about them quickly in a video I did the other day if that’s helpful. There is some extra nuance to them, like an import map key can replace only part of an import path if that’s helpful.

It seems like a relatively small thing in the wide world of web development, but I think it has big implications. I know Baldur Bjarnason definitely thinks so, as he’s got a whole course called Uncluttered: Free yourself from Node with import maps and test-driven web development, which I’ve just started taking myself.


Shout out to Flavio Copes for the recent release of The Valley of Code which looks like a great reference, up from “first principles”, of web development work. Not a lot of guides like this are so comprehensive the start out with URLs and DNS before going on to higher level languages like HTML and CSS.

There is even a section on using CodePen so color me a little biased.


I shared a video of Szenia Zadvornykh’s Crowd Simulator the other day (just because it’s awesome). It’s just one image if you can believe that. Us early 2010’s developers remember sprite sheets.

It’s almost like those illustrated people are the result of some fancy drawing algorithm. Well, here’s something like that! CSS-PEEPS is “one CSS file, one <div>, and over 5 MILLION combinations” of people.

That’s me in 2 years when I inevitably need glasses and try hair regrowth techniques doing my “well actually” look.


A bit of a random add-on here, but I think I agree with Evert Pot when he says:

So while React/Next.js may be relegated to the enterprise and legacy systems in a few years, they completely transformed front-end development and created ripple effects in many other technologies. One of many great ideas stemming from this stack is JSX. I think JSX has a chance to stay relevant and useful beyond React/Next.

I’m maybe a little more bullish on Next.js because it does a lot really well, has a lot of momentum, support, and has some DX that is hard to match. But that’s a little besides the point. JSX, as an industry thing, might just end up having bigger influence over time than React itself. It’s just a pretty good HTML templating language.

HTML Layout

Category Image 052

The general appearance of a piece of writing, a picture, a piece of text, or another medium is created to appeal to the spectator and aid in understanding what they are looking at. For instance, Computer Hope has a distinctive layout that is identifiable to our visitors, making it easier for them to move around the website.

What Is an HTML Layout?

An HTML layout is a template for organizing web pages in a specific way. It is straightforward to use, understand, and adjust web design elements using HTML tags. A proper HTML layout is essential for any website and will significantly enhance its visual appeal. They will also be appropriately formatted on mobile devices because HTML layouts are often responsive by default.

Chris’ Corner: Can Has Blurs, Filters, and Masks

Category Image 052

You’ve seen CSS’ new :has() selector right?

There are all these “basic” use cases that leave me smiling because the solutions of the past were inelegant at best, and now are easy and satisfying. Here’s one:

  1. You want margin below your <h2>
  2. … unless there is a <time> right below it, then no margin
  3. … but put the margin below the <time> instead.

One approach to this with :has() is just a few lines and reads very clearly to me:

h2 {
  margin-block-end: 1.2rem;
  &:has(+ time) {
    margin-block-end: 0;
  }
  &:has(+ time) + time {
    margin-block-end: 1.2rem;
  }
}
Demo

But as I mentioned, that’s a “basic” use case. The power of :has() is very deep, as it allows for inspection from any DOM element down the tree, then moving as far back up as needed. It’s a bit hard to wrap my mind around that sometimes. Like Jim Neilsen said:

I feel like lots of people have their own little oh yeah! moments with this CSS feature. Michelle Barker blogged a few of them, including an interesting combination with the :target selector. Does a certain “have” an element that is targetted? Yes? Animate the grid to reveal an otherwise hidden area:

Poking around my own code bases, I can see it starting to sprinkle in. It’s interested to see when it comes to unknown data coming into templates. Here’s a JSX example:

<div class="things">
  {things.map(thing => {
    return(
      <div class="thing" key={thing.id}>
        <h3 data-category={thing.category}>{thing.name}</h3>
        {thing.text}
      </div>
    );
  })
</div>

Here I’ve spat out a total arbitrary list of “things”. I found it most useful to have the data-category attribute on the header, as direct styling with that was useful. But what if the parent needs styling based on that? Easy:

.thing:has([data-category="widget"]) {
   background: yellow;
}

More, what if the entire group needs a special style? And if it does, make all the headers smaller?

.things:has([data-category="widget"]) h3 {
  font-size: 80%;
}

Go down, then back up!

I’m even very-mindblown-y on the idea that quantity queries are now easy. Does this list have at least 11 items in it? Yes? Then do something:

ol:has(li:nth-child(11)) {
  color: red;
}

Get out of dodge.


We’ve had blend modes in CSS for a while. You use mix-blend-mode when you’re trying to blend an elements content with it’s background. You use background-blend-mode when layering backgrounds and wanting to blend those.

It still strikes me as a surprisingly cool thing we have access to directly in CSS. And it’s niche enough that when I see it used, it’s usually a pleasant surprise.

Brad Woods has an awesome article showing off how they work. Interactive examples really does blending justice here.

Those look dramatic, but blending can be rather subtle. Brad has all sorts of other examples like blending gradients over images, blending textures, blending patterns, that all end up quite nice and the fact that they are programmatic just feels cool and powerful.

Koding Kitty has a similarly good explanation page, specifically using provided Tailwind class names to do the blending over top a single-color icon. I always thought Robin did a good job on the old CSS-Tricks page as well.

You know what I always think of with blending, though? It’s not a CSS property that you just learn and then you know it. All you can learn is the general category of what it can do and the circumstances it might be useful. Then, you have to guess and test your way to a good outcome. One might argue a lot of CSS is that way, but I feel like blending is particularly this way.


Speaking of excellent interactive tutorials!

Artur Bień is all over it in Blur Vignette effect in CSS.

How would you pull of this in CSS?

Quickly, it’s to layer two copies of the image, blur the top one, and use a mask to only reveal the edges of the blurred version. The hard part is crafting the mask. Artur does it with 6 layered gradients which gets the effect just right.

Don’t miss the Windows 98-ish interactive demo at the bottom which lets you totally control different Aspects of the effect.

Artur is the master of getting these kind of details right. Check out this quick screencast video he tweeted about getting a “glass overlay” effect just right. I’d be like “that’s what backdrop-filter is for!” That’s in there, but it doesn’t account for the, as Artur puts it:

In reality we see glass objects reflecting light even before they are placed directly between our eyes and the light source.


Well we’ve touched on filters, blending, and masking, I suppose it’s not out of line to hit shadows too. I enjoyed Preethi Sam’s thinking differently in A Few Interesting Ways To Use CSS Shadows For More Than Depth. Shadows can have properties that make them quite un-shadow-like, like having zero blur, being inside an element, being whatever color you want, and even being layered. Like how good are these hovers?

Nothing wrong with using shadows for shadows though! I ran across boxshadows.xyz the other day and it’s a pretty nice tool for doing layered shadows that have a better depth effect than any one shadow alone can do.

I just love that sort of look. It’s intense looking in that exact example, but a slightly more subtle version with a bigger shadow behind and a slight lighting effect inset on top is just a really juicy effect that works well on the web.

You know what else is hot right now? Blurred and colored blobs as backgrounds. As ever, you can make those as images and use a background-image (I’ve seen Figma plugins like this and this). But it’s fun to do programmatically of course as you can color them as needed, make different sizes, and even animate them.

Andrew Walpole took a crack at it with Glowing Blurred Backgrounds with CSS. The filter property does the heavy lifting getting the blurryness going, but getting the shapes together in which to blur is very clever.

Below the (left) turns to the (right).

Chris’ Corner: Things I Totally Didn’t Know About That I Learned From Taking the State of HTML 2023 Survey

Category Image 052

Lea Verou helped craft the State of HTML 2023 Survey — the first of it’s kind! HTML, you say? What is there to ask? HTML isn’t exactly what I’d think of as a fast-moving technology. I hear there is a <search> element now, so that’s new. It’s sugar for <div role="search">. I like it. Is there much more than that? Well lemme just have a click over to the survey and take it for myself. 😳. Uhm yes there is much more than that.

I actually do try to keep up with this sort of thing, and I’ll tell ya going through this survey had me clicking that “🤷 Never heard of it” choice quite a bit. Allow me to pick out a few that surprised me.

  1. I didn’t know you could programmatically open an input’s UI. Like if you have a reference to it, you can dateInput.showPicker(). Funny twist though, you can’t try it within the CodePen editor or else you’ll get a HTMLInputElement::showPicker() called from cross-origin iframe. error. It’ll work fine in Debug Mode though. I don’t think you can declaratively open it, though, right? You should be able to.
  2. I knew that you could make an element “editable” by adding the contenteditable attribute, but I didn’t know you could opt-out of the rich tech formatting with contenteditable="plaintext-only". Looks like everybody but Firefox already has it. Just to make everything about me: consider the UI of the header area of a Pen. If you own it and hover over the title, you can click a little ✎ icon to edit it. We don’t use contenteditable there because I’m worried someone will copy and paste the entire Yahoo! homepage in there (kidding, kinda). But rich text is entirely irrelevant there, and this would be a nice alternative to the text-element-flip-flopped-for-a-text-input like we currently do.
  3. I didn’t know that there is a plan to allow the name attribute across multiple <details> elements, which makes it so only one can be open at a time, a common “accordion” pattern. I somehow thought Safari was going to be first out of the gate with this with v17, but I was wrong. So nobody is shipping it, but I do like it. Clever idea, if a little hard to discover.
  4. I knew about <script> attributes like async and defer but didn’t know about this one that essentially does the opposite: blocking="render". That’s not, like, a great performance characteristic, but if you’ve got JavaScript that really needs to execute before a user sees anything (rather than showing something and having it flop out post-rendering), I could see this being useful. I guess any bundle that includes React should use this eh?
  5. I didn’t know there was a <model> element, for showing 3D models. I have no idea how it works. I would guess it’s largely semantic rather than functional. Seems early.
  6. I knew about JSON imports, like import json from './foo.json' assert { type: 'json' }; which I really like as it saves me from a ceremonious fetch-and-parse. I’ve also heard of the CSS version ala import sheet from './styles.css' assert { type: 'css' }; which then allows me to donk those styles onto the document or any resistant shadowRoot. But I didn’t know the idea was being extended to HTML like import { TabList } from "./tablist.html" with { type: 'html' }; I guess it’ll make it easier to define custom elements that reside within that chunk of HTML? I can’t quite picture it yet so would love to see examples.
  7. I had never heard of the focusgroup attribute. I read up a smidge and it’s very interesting! If I understand it right, it essentially allows you to make a group of focusable elements respond to arrow key navigation within the group. If you hit tab again, you’d leave the whole group of focusable elements. Like a group of radio buttons! But with whatever group you want.

While we’re deep in HTML land here, allow me to reach into my bag of links and share some of the most interesting ones related to HTML I’ve saved lately.


One of the most interesting things starting to arrive in HTML is popovers. “Popovers are everywhere on the web.” says Una Kravets introducing them. Menus, tooltips, button dropdowns, etc. It’s like a little chunk of UI that needs to sit over all the other UI. Often easier said than done! CSS’ darning z-index can only take you so far. If you’re deep in a nested DOM, you often can’t get a bit of popover UI high enough, and need to resort to JavaScript manipulation to move it somewhere higher in the DOM, which has it’s own set of problems, like positioning complexity and accessible connective tissue to what controls it. Native popovers promote themselves onto some magical higher rendering layer which is on top no matter what. Awfully fancy.

I would think anchor positioning goes together with this like peanut butter and jelly. If you just wank the popover in the middle (which I think it does by default)… isn’t that just a <dialog>? Fortunately Hidde de Vries has us covered here with Dialogs and popovers seem similar. How are they different? I wish I could but I can’t do better than his summary so:

OK, so, in summary: modality of a component is a state in which only that component can be used. When something is modal, everything else is inert: blocked from access in any way, unfocusable and usually obscured with a backdrop. Making something modal is a substantial decision, it should be used sparingly. Dialogs can be modal or non-modal (also called modeless). popovers are being proposed by Open UI as a new way to build non-modal dialogs with a specific set of behaviours and characteristics, like top layer presence, JS-less toggleability and browser-provided light dismiss. Unlike <dialog>, a popover does not have a built-in role: as a developer, you can add the popover attribute to the semantically most relevant element

You know what Safari 17 totally does have? Popovers. I’ll paste their example HTML here in case you wanna copy and paste it yourself into a new Pen in Safari 17 and have a test:

<button popovertarget="info-box" popovertargetaction="show">More info</button>

<article id="info-box" popover="auto">
  <h2>Additional Information</h2>
  <p>Here’s something I wanted to tell you.</p>
  <button popovertarget="info-box" popovertargetaction="hide">Close</button>
</article>

Just a quick high-five to Rian Rietveld for a darn fine overview of crafting the perfect link. Introductory content is everywhere, but the good stuff is hard to find (this is from 2021). Not a ton of code examples, which to me makes an article like this even harder to write.

I just used a little link-related trick I nearly forgot about the other day: using the download attribute like:

<a href="/files/pitch.pdf" download>Download Pitch PDF</a>

I think Dave has a good point here: Markdown images are an anti-pattern. I’ve always said images are hard, in part because of how many attributes you need to know about and put on the <img> tag. Just loading="lazy" alone is huge. Some Markdown processors allow for additional attributes in the Markdown syntax, but not all, and the format for that isn’t particularly pretty (and of course, totally non-portable to other processors). Thankfully Markdown supports HTML, so just use that.


I’ll leave you with the html review:

Our 2023 issue is made up of 17 contributions that span modes of digital literature and experiment. We have poetic instruments, interactive fictions, illustrated essays, movable lyrics, linguistic gardens, and pixelated memories.

How to Solve: “Avoid an Excessive DOM Size” in WordPress

Category Image 052
Avoid an excessive dom size.Google Lighthouse’s ‘Avoid an excessive DOM size’ warning is a performance metric you’ll want to look out for. The Document Object Model (DOM) Aspect of this lets scripts access and manipulate the elements of your site. This includes your HTML and CSS. However, a large DOM size can cause performance issues, maintenance problems, and much more. First, let’s help you understand what it means.

Gradients, Blend Modes, And A Really Cool Hover Effect

Category Image 052

Do you know how box-shadow is sometimes used as a hover effect? It adds depth to something, like a button, and can create the impression that it is being pressed into the page.

Let’s pull it all together:

See the Pen Gradient Hover [forked] by Preethi Sam.

Have fun with this! Try different colors and different gradations. Here’s another example where the gradient is applied to text elements:

See the Pen Gradient Hover 2 [forked] by Preethi Sam.

Aesthetics aside, remember to use designs, layouts, and colors that make it clear to users that they are interacting with the element and what that interaction does.

Further Reading On SmashingMag

Chris’ Corner: Web Components Don’t Need You

Category Image 052

Dave Rupert blogged a bunch of reasons about why you probably aren’t using them yet. Some of it is technological, and more of it is historical, marketing, and psychological reasons. Then Dave, a pretty avid Web Components follower and advocate, followed up with another surprise. Should you rewrite your app to use them? Probably not.

It’s not that you shouldn’t use them because they aren’t good, it’s:

If your components only have one place to go, then you probably don’t need Web Components. Even if your components service a couple different apps or product teams that all use the same uniform tech stack, you probably don’t need Web Components. Where Web Components shine is when your components need to go to many places.

The grid of logos on https://arewebcomponentsathingyet.com/ tells that story: very big companies.

Nolan Lawson followed that up with Use web components for what they’re good at, a more specific take on this, which largely agrees with Dave. Big companies are reaching for them because they solve actual problems for them. But enterprise isn’t very present on social media, so you just don’t hear about it as much.

So why are big enterprises so gaga for web components? For one thing, design systems based on web components work across a variety of environments. A big company might have frontends written in React, Angular, Ember, and static HTML, and they all have to play nicely with the company’s theming and branding. The big rewrite (as described above) may be a fun exercise for your average startup, but it’s just not practical in the enterprise world.

Having a lot of consumers of your codebase, and having to think on longer timescales, just leads to different technical decisions. And to me, this points to the main reason enterprises love web components: stability and longevity.

If you have some of those problems, you’ll probably benefit from Web Components and could or should use them, or maybe you already are. If not, whatever. Nobody needs permission to use them, and plenty of companies are doing it without a single care about what the social media vibe is on them. Web Components still have some problems, and fortunately, are still being actively worked on, so the story should get better year after year, in case you’re on the fence and watching.

Nolan does shout out one thing Web Components excel at, obviously and immediately:

To me, [client-rendered leaf components are] the most unambiguously slam-dunk use case for web components. You have some component at the leaf of the DOM tree, it doesn’t need to be rendered server-side, and it doesn’t <slot> any content inside of it. Examples include: a rich text editor, a calendar widget, a color picker, etc.

Dan Ryan has another take: they can be really simple. He used a header component as an example, which didn’t buy them anything extreme — just a simple update for a simple benefit.

So what did this gain us? For this example not much really. But where it really shines for us is only loading the CSS needed for the components used on a given page. Most of our visitors only view a single campaign page which uses just a few components. Previously though we were bundling all our CSS into a single file and serving it to everyone.

I’m the biggest fan of Web Components when you can just pluck one off the shelf and use it for something useful, knowing it’s lightweight and flexible. Nolan’s own emoji-picker-element is a classic example. When I see one-off componentry that isn’t a Web Component lately, I immediately wish it was. Check out this OverlayScrollbars “plugin”. Wouldn’t it be awesome as an <overlay-scrollbars> component, making it declarative and easy to use? (Yes.)

But I’m down for bigger approaches, too, so long as they are solving a problem. Google’s Material Design is an example of that. Material 3 is their latest take, and it seems to be mostly leaning into native apps, with the web version “coming soon”. When it does, it appears as if it’ll be Web Components-based. That’s cool and maybe even a touch surprising, being that Google could have used it as a way to promote Angular. But just because they went Web Components doesn’t mean they didn’t go Angular, assuming they work nicely in Angular, which let’s just hope they do.

Allow me to end with a little linky-uppy: Tram-Lite. I really like how you just use HTML to define the whole component, then go on to use it elsewhere. It requires no build process and has a very native feeling. Actually, check out the principals — they seem sound to me. And I’m not just saying that because it works great on CodePen.

Chris’ Corner: Even More SVG Tricks

Category Image 052

SVG has so many tricks up its sleeve. It’s really a full-featured drawing API literally designed for the web, but few of us really truly understand it nor reach for it enough. Heck, I even wrote a book about it, and I don’t. At the time, just getting people to use SVG for icons felt like an uphill battle, but thankfully, I think that one has been won.

Let’s look at some cool SVG examples that have crossed my desk lately.


Animate an SVG Shape’s Inner Stroke

Christopher Kirk-Nielsen with a variety of demos that are a good reminder that stroke is animatable (like the width of it), which can do some cool effects. This demo looks like the classic one where the stroke moves to the inside only, filling the shapes.

It was only in the last few years that browsers like Chrome GPU accelerated SVG animations, making stuff like this really smooth.


SVG viewBox padding

To be clear, the viewBox in SVG does not actually have padding. But it’s an important thing to think about. Chuan makes the point that if you make a 10✕10 area via the viewBox, then make a<rect> that fills that 10✕10 area, the stroke around it, the stroke will be half cut off. That’s because stroke straddles the edge of shapes in SVG. So you either gotta monkey with the coordinates of the shapes, or you gotta adjust the viewBox to handle it. Chuan’s thinking is: let a processor handle it.

viewBox="0 0 10 10 padding .5"

/* translates to */

viewBox="-.5 -.5 11 11"

Clever thinking, really. The CSS Doodle tool can do it.


So… you can set an SVG circle’s radius in CSS?

The very basic answer to Paul Hebert’s questions is: yeah, totally. Like if you have this:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="50" />
</svg>

You can adjust the radius in CSS like:

circle {
  r: 20;
}

Not CSS you see every day, but yeah, that’s totally fine. But Paul’s point is that normally you set the radius in the SVG code, but you might need it in CSS code. Like, a way to keep them in sync is good. In Paul’s demo, it looks like he doesn’t even set the radius in SVG at all, just does it in CSS via a --radius Custom Property, then uses that in the other calculations needed to make these percentage meters work.


Understanding SVG Paths

The <path> element in SVG is the most complicated of the shape drawing elements. In fact, as I understand it, all the other elements are just syntactic sugar over a path anyway. I once wrote An Illustrated Guide when I was learning it and figuring it out. But Nanda Syahrasyad has outdone me easily in Understanding SVG Paths.

The trick is understanding the commands. They are pretty understandable in the end, as it were. They are like “pick the pen up and move it here, then draw a line over to here” or “starting where you are, move the pen in this direction this far” or “draw a curve from here to there using these other two points as essentially gravitational poles.”

If you get into it, you’ll find yourselves (gasp) drawing your own shapes. I love Nanda’s opener:

I think you’re kind of a next-level front-end developer if you’re building bending ass buttons like that.


SVGs have additional pointer-events properties

If you’re like me, you think of pointer-events in CSS as a thing you use to set none once in a while. Maybe you set some colored overlay <div> over something, but you don’t want it to actually eat up clicks, so you set pointer-events: none on it and those clicks will slide right through.

When it comes to SVG, though, Stefan Judis has noted some additional values for it that are specific to SVG like:

.foo {
  pointer-events: visiblePainted;        
}

The demo by Martijn Cuppens helps. See number 4. Like ONLY the “painted” part is clickable there. I feel like that opens up some weird “click map” possibilities, so please send them to me if you do something weird.