Css Tricks
CSS Tricks That Use Only One Gradient
CSS gradients have been so long that there’s no need to rehash what they are and how to use them. You have surely encountered them at some point in your front-end journey, and if you follow me, you also know that I use them all the time. I use them for CSS patterns, nice CSS decorations, and even CSS loaders. But even so, gradients have a tough syntax that can get very complicated very quickly if you’re not paying attention.
In this article, we are not going to make complex stuff with CSS gradients. Instead, we’re keeping things simple and I am going to walk through all of the incredible things we can do with just one gradient.
Only one gradient? In this case, reading the doc should be enough, no?
No, not really. Follow along and you will see that gradients are easy at their most basic, but are super powerful if we push them — or in this case, just one — to their limits.
CSS patternsOne of the first things you learn with gradients is that we can establish repeatable patterns with them. You’ve probably seen some examples of checkerboard patterns in the wild. That’s something we can quickly pull off with a single CSS gradient. In this case, we can reach for the repeating-conic-gradient() function:
background: repeating-conic-gradient(#000 0 25%, #fff 0 50%) 0 / 100px 100px;A more verbose version of that without the background shorthand:
background-image: repeating-conic-gradient(#000 0 25%, #fff 0 50%); background-size: 100px 100px;Either way, the result is the same:
CodePen Embed FallbackPretty simple so far, right? You have two colors that you can easily swap out for other colors, plus the background-size property to control the square shapes.
If we change the color stops — where one color stops and another starts — we get another cool pattern based on triangles:
background: repeating-conic-gradient(#000 0 12.5%, #fff 0 25%) 0 / 100px 100px; CodePen Embed FallbackIf you compare the CSS for the two demos we’ve seen so far, you’ll see that all I did was divide the color stops in half, 25% to 12.5% and 50% to 25%.
Another one? Let’s go!
CodePen Embed FallbackThis time I’m working with CSS variables. I like this because variables make it infinitely easier to configure the gradients by updating a few values without actually touching the syntax. The calculation is a little more complex this time around, as it relies on trigonometric functions to get accurate values.
I know what you are thinking: Trigonometry? That sounds hard. That is certainly true, particularly if you’re new to CSS gradients. A good way to visualize the pattern is to disable the repetition using the no-repeat value. This isolates the pattern to one instance so that you clearly see what’s getting repeated. The following example declares background-image without a background-size so you can see the tile that repeats and better understand each gradient:
CodePen Embed FallbackI want to avoid a step-by-step tutorial for each and every example we’re covering so that I can share lots more examples without getting lost in the weeds. Instead, I’ll point you to three articles you can refer to that get into those weeds and allow you to pick apart our examples.
- How to create background patterns using CSS & conic-gradient (Verpex blog)
- Learn CSS radial-gradient by Building Background Patterns (freeCodeCamp)
- Background Patterns, Simplified by Conic Gradients (Ana Tudor)
I’ll also encourage you to open my online collection of patterns for even more examples. Most of the examples are made with multiple gradients, but there are plenty that use only one. The goal of this article is to learn a few “single gradient” tricks — but the ultimate goal is to be able to combine as many gradients as possible to create cool stuff!
Grid linesLet’s start with the following example:
CodePen Embed FallbackYou might claim that this belongs under “Patterns” — and you are right! But let’s make it more flexible by adding variables for controlling the thickness and the total number of cells. In other words, let’s create a grid!
.grid-lines { --n: 3; /* number of rows */ --m: 5; /* number of columns */ --s: 80px; /* control the size of the grid */ --t: 2px; /* the thickness */ width: calc(var(--m)*var(--s) + var(--t)); height: calc(var(--n)*var(--s) + var(--t)); background: conic-gradient(from 90deg at var(--t) var(--t), #0000 25%, #000 0) 0 0/var(--s) var(--s); }First of all, let’s isolate the gradient to better understand the repetition (like we did in the previous section).
CodePen Embed FallbackOne repetition will give us a horizontal and a vertical line. The size of the gradient is controlled by the variable --s, so we define the width and height as a multiplier to get as many lines as we want to establish the grid pattern.
What’s with “+ var(--t)” in the equation?
The grid winds up like this without it:
CodePen Embed FallbackWe are missing lines at the right and the bottom which is logical considering the gradient we are using. To fix this, the gradient needs to be repeated one more time, but not at full size. For this reason, we are adding the thickness to the equation to have enough space for the extra repetition and the get the missing lines.
CodePen Embed FallbackAnd what about a responsive configuration where the number of columns depends on the available space? We remove the --m variable and define the width like this:
width: calc(round(down, 100%, var(--s)) + var(--t));Instead of multiplying things, we use the round() function to tell the browser to make the element full width and round the value to be a multiple of --s. In other words, the browser will find the multiplier for us!
Resize the below and see how the grid behaves:
CodePen Embed FallbackIn the future, we will also be able to do this with the calc-size() function:
width: calc-size(auto, round(down, size, var(--s)) + var(--t));Using calc-size() is essentially the same as the last example, but instead of using 100% we consider auto to be the width value. It’s still early to adopt such syntax. You can test the result in the latest version of Chrome at the time of this writing:
CodePen Embed Fallback Dashed linesLet’s try something different: vertical (or horizontal) dashed lines where we can control everything.
.dashed-lines { --t: 2px; /* thickness of the lines */ --g: 50px; /* gap between lines */ --s: 12px; /* size of the dashes */ background: conic-gradient(at var(--t) 50%, #0000 75%, #000 0) var(--g)/calc(var(--g) + var(--t)) var(--s); } CodePen Embed FallbackCan you figure out how it works? Here is a figure with hints:
Try creating the horizontal version on your own. Here’s a demo that shows how I tackled it, but give it a try before peeking at it.
What about a grid with dashed lines — is that possible?
Yes, but using two gradients instead of one. The code is published over at my collection of CSS shapes. And yes, the responsive behavior is there as well!
CodePen Embed Fallback Rainbow gradientHow would you create the following gradient in CSS?
You might start by picking as many color values along the rainbow as you can, then chaining them in a linear-gradient:
linear-gradient(90deg, red, yellow, green, /* etc. */, red);Good idea, but it won’t get you all the way there. Plus, it requires you to juggle color stops and fuss with them until you get things just right.
There is a simpler solution. We can accomplish this with just one color!
background: linear-gradient(90deg in hsl longer hue, red 0 0);I know, the syntax looks strange if you’re seeing the new color interpolation for the first time.
If I only declare this:
background: linear-gradient(90deg, red, red); /* or (90deg, red 0 0) */…the browser creates a gradient that goes from red to red… red everywhere! When we set this “in hsl“, we’re changing the color space used for the interpolation between the colors:
background: linear-gradient(90deg in hsl, red, red);Now, the browser will create a gradient that goes from red to red… this time using the HSL color space rather than the default RGB color space. Nothing changes visually… still see red everywhere.
The longer hue bit is what’s interesting. When we’re in the HSL color space, the hue channel’s value is an angle unit (e.g., 25deg). You can see the HSL color space as a circle where the angle defines the position of the color within that circle.
Since it’s a circle, we can move between two points using a “short” path or “long” path.
If we consider the same point (red in our case) it means that the “short” path contains only red and the “long” path runs into all the colors as it traverses the color space.
CodePen Embed FallbackAdam Argyle published a very detailed guide on high-definition colors in CSS. I recommend reading it because you will find all the features we’re covering (this section in particular) to get more context on how everything comes together.
We can use the same technique to create a color wheel using a conic-gradient:
background: conic-gradient(in hsl longer hue,red 0 0); CodePen Embed FallbackAnd while we are on the topic of CSS colors, I shared another fun trick that allows you to define an array of color values… yes, in CSS! And it only uses a single gradient as well.
Hover effectsLet’s do another exercise, this time working with hover effects. We tend to rely on pseudo-elements and extra elements when it comes to things like applying underlines and overlays on hover, and we tend to forget that gradients are equally, if not more, effective for getting the job done.
Case in point. Let’s use a single gradient to form an underline that slides on hover:
h3 { background: linear-gradient(#1095c1 0 0) no-repeat var(--p,0) 100%/var(--p, 0) .1em; transition: 0.4s, background-position 0s; } h3:hover { --p: 100%; } CodePen Embed FallbackYou likely would have used a pseudo-element for this, right? I think that’s probably how most people would approach it. It’s a viable solution but I find that using a gradient instead results in cleaner, more concise CSS.
You might be interested in another article I wrote for CSS-Tricks where I use the same technique to create a wide variety of cool hover effects.
CSS shapesCreating shapes with gradients is my favorite thing to do in CSS. I’ve been doing it for what feels like forever and love it so much that I published a “Modern Guide for Making CSS Shapes” over at Smashing Magazine earlier this year. I hope you check it out not only to learn more tricks but to see just how many shapes we can create with such a small amount of code — many that rely only on a single CSS gradient.
Some of my favorites include zig-zag borders:
CodePen Embed Fallback…and “scooped” corners:
CodePen Embed Fallback…as well as sparkles:
CodePen Embed Fallback…and common icons like the plus sign:
CodePen Embed FallbackI won’t get into the details of creating these shapes to avoid making this article long and boring. Read the guide and visit my CSS Shape collection and you’ll have everything you need to make these, and more!
Border image tricksLet’s do one more before we put a cap on this. Earlier this year, I discovered how awesome the CSS border-image property is for creating different kinds of decorations and shapes. And guess what? border-image limits us to using just one gradient, so we are obliged to follow that restriction.
Again, just one gradient and we get a bunch of fun results. I’ll drop in my favorites like I did in the last section. Starting with a gradient overlay:
CodePen Embed FallbackWe can use this technique for a full-width background:
CodePen Embed Fallback…as well as heading dividers:
CodePen Embed Fallback…and even ribbons:
CodePen Embed FallbackAll of these have traditionally required hacks, magic numbers, and other workarounds. It’s awesome to see modern CSS making things more effortless. Go read my article on this topic to find all the interesting stuff you can make using border-image.
Wrapping upI hope you enjoyed this collection of “single-gradient” tricks. Most folks I know tend to use gradients to create, well, gradients. But as we’ve seen, they are more powerful and can be used for lots of other things, like drawing shapes.
I like to add a reminder at the end of an article like this that the goal is not to restrict yourself to using one gradient. You can use more! The goal is to get a better handle on how gradients work and push them in interesting ways — that, in turn, makes us better at writing CSS. So, go forth and experiment — I’d love to see what you make!
CSS Tricks That Use Only One Gradient originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
WPGraphQL Becomes a Canonical Plugin: My Move to Automattic
It’s always a gas when a good person doing good work gets a good deal. In this case, Jason’s viral WPGraphQL plugin has not only become a canonical WordPress plugin, but creator Jason Bahl is joining Automattic as well.
I’m linking this up because it’s notable for a few reasons:
- WPGraphQL is a must-have plugin for creating headless WordPress sites and making it a canonical plugin is WordPress making a big step in that direction.
- Jason is leaving WP Engine to join Automattic and we all know what a dumpster fire that relationship has become. (WP Engine also has a big foot in headless WordPress.)
- You might be hearing about canonical plugins for the first time.
Congrats, Jason! I didn’t know you were in Denver — maybe we’ll bump into each other and I can give you a well-deserved high-five. ✋
WPGraphQL Becomes a Canonical Plugin: My Move to Automattic originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
2024: More CSS At-Rules Than the Past Decade Combined
More times than I can count, while writing, I get myself into random but interesting topics with little relation to the original post. In the end, I have to make the simple but painful choice of deleting or archiving hours of research and writing because I know most people click on a post with a certain expectation of what they’ll get, and I know it isn’t me bombing them with unrelated rants about CSS.
This happened to me while working on Monday’s article about at-rules. All I did there was focus on a number of recipes to test browser support for CSS at-rules. In the process, I began to realize, geez we have so many new at-rules — I wonder how many of them are from this year alone. That’s the rabbit hole I found myself in once I wrapped up the article I was working on.
And guess what, my hunch was right: 2024 has brought more at-rules than an entire decade of CSS.
It all started when I asked myself why we got a selector() wrapper function for the @supports at-rule but are still waiting for an at-rule() version. I can’t pinpoint the exact reasoning there, but I’m certain there wasn’t much of a need to check the support of at-rules because, well, there weren’t that many of them — it’s just recently that we got a windfall of at-rules.
Some historical contextSo, right around 1998 when the CSS 2 recommendation was released, @import and @page were the only at-rules that made it into the CSS spec. That’s pretty much how things remained until the CSS 2.1 recommendation in 2011 introduced @media. Of course, there were other at-rules like — @font-face, @namespace and @keyframes to name a few — that had already debuted in their own respective modules. By this time, CSS dropped semantic versioning, and the specification didn’t give a true picture of the whole, but rather individual modules organized by feature.
Random tangent: The last accepted consensus says we are at “CSS 3”, but that was a decade ago and some even say we should start getting into CSS 5. Wherever we are is beside the point, although it’s certainly a topic of discussion happening. Is it even useful to have a named version?
The @supports at-rule was released in 2011 in CSS Conditional Rules Module Level 3 — Levels 1 and 2 don’t formally exist but refer to the original CSS 1 and 2 recommendations. We didn’t actually get support for it in most browsers until 2015, and at that time, the existing at-rules already had widespread support. The @supports was only geared towards new properties and values, designed to test browser support for CSS features before attempting to apply styles.
The numbersAs of today, we have a grand total of 18 at-rules in CSS that are supported by at least one major browser. If we look at the year each at-rule was initially defined in a CSSWG Working Draft, we can see they all have been published at a fairly consistent rate:
If we check the number of at-rules supported on each browser per year, however, we can see the massive difference in browser activity:
If we just focus on the last year a major browser shipped each at-rule, we will notice that 2024 has brought us a whopping seven at-rules to date!
Data collected from caniuse.I like little thought experiments like this. Something you’re researching leads to researching about the same topic; out of scope, but tangentially related. It may not be the sort of thing you bookmark and reference daily, but it is good cocktail chatter. If nothing else, it’s affirming the feeling that CSS is moving fast, like really fast in a way we haven’t seen since CSS 3 first landed.
It also adds context for the CSS features we have — and don’t have. There was no at-rule() function initially because there weren’t many at-rules to begin with. Now that we’ve exploded with more new at-rules than the past decade combined, it may be no coincidence that just last week the Chrome Team updated the function’s status from New to Assigned!
One last note: the reason I’m even thinking about at-rules at all is that we’ve updated the CSS Almanac, expanding it to include more CSS features including at-rules. I’m trying to fill it up and you can always help by becoming a guest writer.
2024: More CSS At-Rules Than the Past Decade Combined originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Smashing Hour With Heydon Pickering
I sat down with Heydon Pickering in the most recent episode of the Smashing Hour. Full transparency: I was nervous as heck. I’ve admired Heydon’s work for years, and even though we run in similar circles, this was our first time meeting. You know how you build some things up in your mind and sorta psyche yourself out? Yeah, that.
Heydon is nothing short of a gentleman and, I’ll be darned, easy to talk to. As is the case with any Smashing Hour, there’s no script, no agenda, no nothing. We find ourselves getting into the weeds of accessibility testing and documentation — or the lack of it — before sliding into the stuff he’s really interested in and excited about today: styling sound. Dude pulled out a demo and walked me (and everyone else) through the basics of the Web Audio API and how he’s using it to visualize sounds in tons of groovy ways that I now want hooked up to my turntable somehow.
Smashing Hour With Heydon Pickering originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Searching for a New CSS Logo
There is an amazing community effort happening in search of a new logo for CSS. I was a bit skeptical at first, as I never really considered CSS a “brand.” Why does it need a logo? For starters, the current logo seems… a bit dated.
Displayed quite prominently is the number 3. As in CSS version 3, or simply CSS3. Depending on your IDE’s selected icon pack of choice, CSS file icons are often only the number 3.
To give an incredibly glossed-over history of CSS3:
- Earliest draft specification was in 1999!
- Adoption began in 2011, when it was published as the W3C Recommendation.
- It’s been used ever since? That can’t be right…
CSS is certainly not stuck in 2011. Take a look at all the features added to CSS in the past five years (warning, scrolling animation ahead):
CodePen Embed Fallback(Courtesy of Alex Riviere)
Seems like this stems mainly from the discontinuation of version numbering for CSS. These days, we mostly reference newer CSS features by their individual specification level, such as Selectors Level 4 being the current Selectors specification, for example.
A far more general observation on the “progress” of CSS could be taking a look at features being implemented — things like Caniuse and Baseline are great for seeing when certain browsers implemented certain features. Similarly, the Interop Project is a group consisting of browsers figuring out what to implement next.
There are ongoing discussions about the “eras” of CSS, though, and how those may be a way of framing the way we refer to CSS features.
Chris posted about CSS4 here on CSS-Tricks (five years ago!), discussing how successful CSS3 was from a marketing perspective. Jen Simmons also started a discussion back in 2020 on the CSS Working Group’s GitHub about defining CSS4. Knowing that, are you at least somewhat surprised that we have blown right by CSS4 and are technically using CSS5?
The CSS-Next Community Group is leading the charge here, something that member Brecht de Ruyte introduced earlier this year at Smashing Magazine. The purpose of this group is to, well, determine what’s next for CSS! The group defines the CSS versions as:
- CSS3 (~2009-2012): Level 3 CSS specs as defined by the CSSWG
- CSS4 (~2013-2018): Essential features that were not part of CSS3, but are already a fundamental part of CSS.
- CSS5 (~2019-2024): Newer features whose adoption is steadily growing.
- CSS6 (~2025+): Early-stage features that are planned for future CSS.
Check out this slide deck from November 2023 detailing the need for defining stronger versioning. Their goals are clear in my opinion:
- Help developers learn CSS.
- Help educators teach CSS.
- Help employers define modern web skil…
- Help the community understand the progression of CSS capabilities over time.
Circling back around to the logo, I have to agree: Yes, it’s time for a change.
Back in August, Adam Argyle opened an issue on the CSS-Next project on GitHub to drum up ideas. The thread is active and ongoing, though appears to be honing in on a release candidate. Let’s take a look at some proposals!
Nils Binder, from 9elements, proposed this lovely design, riffing on the “cascade.” Note the river-like “S” shape flowing through the design.
Chris Kirk-Nielson pitched a neat interactive logo concept he put together a while back. The suggestion plays into the “CSS is Awesome” meme, where the content overflows the wrapper. While playful and recognizable, Nils raised an excellent point:
Regarding the reference to the ‘CSS IS AWESOME’ meme, I initially chuckled, of course. However, at the same time, the meme also represents CSS as something quirky, unpredictable, and full of bugs. I’m not sure if that’s the exact message that needs to be repeated in the logo. It feels like it reinforces the recurring ‘CSS is broken’ mantra. To exaggerate: CSS is subordinate to JS and somehow broken.
Wow, is this the end of an era for the familiar meme?
It’s looking that way, as the current candidate builds off of Javi Aguilar’s proposal. Javi’s design is being iterated upon by the group, it’s shaping up and looks great hanging with friends:
Javi describes the design considerations in the thread. Personally, I’m a fan of the color choice, and the softer shape differentiates it from the more rigid JavaScript and Typescript logos.
As mentioned, the discussion is ongoing and the design is actively being worked on. You can check out the latest versions in Adam’s CodePen demo:
Or if checking out design files is more your speed, take a look in Figma.
CodePen Embed FallbackI think the thing that impresses me most about community initiatives like this is the collaboration involved. If you have opinions on the design of the logo, feel free to chime in on the discussion thread!
Once the versions are defined and the logo finalized, the only thing left to decide on will be a mascot for CSS. A chameleon? A peacock? I’m sure the community will choose wisely.
Searching for a New CSS Logo originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSS Anchor Positioning Guide
Not long ago, if we wanted a tooltip or popover positioned on top of another element, we would have to set our tooltip’s position to something other than static and use its inset/transform properties to place it exactly where we want. This works, but the element’s position is susceptible to user scrolls, zooming, or animations since the tooltip could overflow off of the screen or wind up in an awkward position. The only way to solve this was using JavaScript to check whenever the tooltip goes out of bounds so we can correct it… again in JavaScript.
CSS Anchor Positioning gives us a simple interface to attach elements next to others just by saying which sides to connect — directly in CSS. It also lets us set a fallback position so that we can avoid the overflow issues we just described. For example, we might set a tooltip element above its anchor but allow it to fold underneath the anchor when it runs out of room to show it above.
Anchor positioning is different from a lot of other features as far as how quickly it’s gained browser support: its first draft was published on June 2023 and, just a year later, it was released on Chrome 125. To put it into perspective, the first draft specification for CSS variables was published in 2012, but it took four years for them to gain wide browser support.
So, let’s dig in and learn about things like attaching target elements to anchor elements and positioning and sizing them.
Quick reference /* Define an anchor element */ .anchor { anchor-name: --my-anchor; } /* Anchor a target element */ .target { position: absolute; position-anchor: --my-anchor; } /* Position a target element */ .target { position-area: start end; } Table of contents- Basics and terminology
- Attaching targets to anchors
- Positioning targets
- Setting fallback positions
- Custom position and size fallbacks
- Multiple anchors
- Accessibility
- Browser support
- Spec changes
- Known bugs
- Almanac references
- Further reading
At its most basic, CSS Anchor Positioning introduces a completely new way of placing elements on the page relative to one another. To make our lives easier, we’re going to use specific names to clarify which element is connecting to which:
- Anchor: This is the element used as a reference for positioning other elements, hence the anchor name.
- Target: This is an absolutely positioned element placed relative to one or more anchors. The target is the name we will use from now on, but you will often find it as just an “absolutely positioned element” in the spec.
For the following code examples and demos, you can think of these as just two <div> elements next to one another.
<div class="anchor">anchor</div> <div class="target">target</div>CSS Anchor Positioning is all about elements with absolute positioning (i.e., position: absolute), so there are also some concepts we have to review before diving in.
- Containing Block: This is the box that contains the elements. For an absolute element, the containing block is the viewport the closest ancestor with a position other than static or certain values in properties like contain or filter.
- Inset-Modified Containing Block (IMCB): For an absolute element, inset properties (top, right, bottom, left, etc.) reduce the size of the containing block into which it is sized and positioned, resulting in a new box called the inset-modified containing block, or IMCB for short. This is a vital concept to know since properties we’re covering in this guide — like position-area and position-try-order — rely on this concept.
We’ll first look at the two properties that establish anchor positioning. The first, anchor-name, establishes the anchor element, while the second, position-anchor, attaches a target element to the anchor element.
anchor-nameA normal element isn’t an anchor by default — we have to explicitly make an element an anchor. The most common way is by giving it a name, which we can do with the anchor-name property.
anchor-name: none | <dashed-ident>#The name must be a <dashed-ident>, that is, a custom name prefixed with two dashes (--), like --my-anchor or --MyAnchor.
.anchor { anchor-name: --my-anchor; }This gives us an anchor element. All it needs is something anchored to it. That’s what we call the “target” element which is set with the position-anchor property.
position-anchorThe target element is an element with an absolute position linked to an anchor element matching what’s declared on the anchor-name property. This attaches the target element to the anchor element.
position-anchor: auto | <anchor-element>It takes a valid <anchor-element>. So, if we establish another element as the “anchor” we can set the target with the position-anchor property:
.target { position: absolute; position-anchor: --my-anchor; }Normally, if a valid anchor element isn’t found, then other anchor properties and functions will be ignored.
Positioning targetsNow that we know how to establish an anchor-target relationship, we can work on positioning the target element in relation to the anchor element. The following two properties are used to set which side of the anchor element the target is positioned on (position-area) and conditions for hiding the target element when it runs out of room (position-visibility).
position-areaThe next step is positioning our target relative to its anchor. The easiest way is to use the position-area property, which creates an imaginary 3×3 grid around the anchor element and lets us place the target in one or more regions of the grid.
position-area: auto | <position-area>It works by setting the row and column of the grid using logical values like start and end (dependent on the writing mode); physical values like top, left, right, bottom and the center shared value, then it will shrink the target’s IMCB into the region of the grid we chose.
.target { position-area: top right; /* or */ position-area: start end; }Logical values refer to the containing block’s writing mode, but if we want to position our target relative to its writing mode we would prefix it with the self value.
.target { position-area: self-start self-end; }There is also the center value that can be used in every axis.
.target { position-area: center right; /* or */ position-area: start center; }To place a target across two adjacent grid regions, we can use the prefix span- on any value (that isn’t center) a row or column at a time.
.target { position-area: span-top left; /* or */ position-area: span-start start; }Finally, we can span a target across three adjacent grid regions using the span-all value.
.target { position-area: bottom span-all; /* or */ position-area: end span-all; }You may have noticed that the position-area property doesn’t have a strict order for physical values; writing position-area: top left is the same as position-area: left top, but the order is important for logical value since position-area: start end is completely opposite to position-area: end start.
We can make logical values interchangeable by prefixing them with the desired axis using y-, x-, inline- or block-.
.target { position-area: inline-end block-start; /* or */ position-area: y-start x-end; } CodePen Embed Fallback CodePen Embed Fallback position-visibilityIt provides certain conditions to hide the target from the viewport.
position-visibility: always | anchors-visible | no-overflow- always: The target is always displayed without regard for its anchors or its overflowing status.
- no-overflow: If even after applying the position fallbacks, the target element is still overflowing its containing block, then it is strongly hidden.
- anchors-visible: If the anchor (not the target) has completely overflowed its containing block or is completely covered by other elements, then the target is strongly hidden.
Once the target element is positioned against its anchor, we can give the target additional instructions that tell it what to do if it runs out of space. We’ve already looked at the position-visibility property as one way of doing that — we simply tell the element to hide. The following two properties, however, give us more control to re-position the target by trying other sides of the anchor (position-try-fallbacks) and the order in which it attempts to re-position itself (position-try-order).
The two properties can be declared together with the position-try shorthand property — we’ll touch on that after we look at the two constituent properties.
position-try-fallbacksThis property accepts a list of comma-separated position fallbacks that are tried whenever the target overflows out of space in its containing block. The property attempts to reposition itself using each fallback value until it finds a fit or runs out of options.
position-try-fallbacks: none | [ [<dashed-ident> || <try-tactic>] | <'inset-area'> ]#- none: Leaves the target’s position options list empty.
- <dashed-ident>: Adds to the options list a custom @position-try fallback with the given name. If there isn’t a matching @position-try, the value is ignored.
- <try-tactic>: Creates an option list by flipping the target’s current position on one of three axes, each defined by a distinct keyword. They can also be combined to add up their effects.
- The flip-block keyword swaps the values in the block axis.
- The flip-inline keyword swaps the values in the inline axis.
- The flip-start keyword swaps the values diagonally.
- <dashed-ident> || <try-tactic>: Combines a custom @try-option and a <try-tactic> to create a single-position fallback. The <try-tactic> keywords can also be combined to sum up their effects.
- <"position-area"> Uses the position-area syntax to move the anchor to a new position.
This property chooses a new position from the fallback values defined in the position-try-fallbacks property based on which position gives the target the most space. The rest of the options are reordered with the largest available space coming first.
position-try-order: normal | most-width | most-height | most-block-size | most-inline-sizeWhat exactly does “more space” mean? For each position fallback, it finds the IMCB size for the target. Then it chooses the value that gives the IMCB the widest or tallest size, depending on which option is selected:
- most-width
- most-height
- most-block-size
- most-inline-size
This is a shorthand property that combines the position-try-fallbacks and position-try-order properties into a single declaration. It accepts first the order and then the list of possible position fallbacks.
position-try: < "position-try-order" >? < "position-try-fallbacks" >;So, we can combine both properties into a single style rule:
.target { position-try: most-width --my-custom-position, flip-inline, bottom left; } Custom position and size fallbacks @position-tryThis at-rule defines a custom position fallback for the position-try-fallbacks property.
@position-try <dashed-ident> { <declaration-list> }It takes various properties for changing a target element’s position and size and grouping them as a new position fallback for the element to try.
Imagine a scenario where you’ve established an anchor-target relationship. You want to position the target element against the anchor’s top-right edge, which is easy enough using the position-area property we saw earlier:
.target { position: absolute; position-area: top right; width: 100px; }See how the .target is sized at 100px? Maybe it runs out of room on some screens and is no longer able to be displayed at anchor’s the top-right edge. We can supply the .target with the fallbacks we looked at earlier so that it attempts to re-position itself on an edge with more space:
.target { position: absolute; position-area: top right; position-try-fallbacks: top left; position-try-order: most-width; width: 100px; }And since we’re being good CSSer’s who strive for clean code, we may as well combine those two properties with the position-try shorthand property:
.target { position: absolute; position-area: top right; position-try: most-width, flip-inline, bottom left; width: 100px; }So far, so good. We have an anchored target element that starts at the top-right corner of the anchor at 100px. If it runs out of space there, it will look at the position-try property and decide whether to reposition the target to the anchor’s top-left corner (declared as flip-inline) or the anchor’s bottom-left corner — whichever offers the most width.
But what if we want to simulataneously re-size the target element when it is re-positioned? Maybe the target is simply too dang big to display at 100px at either fallback position and we need it to be 50px instead. We can use the @position-try to do exactly that:
@position-try --my-custom-position { position-area: top left; width: 50px; }With that done, we now have a custom property called --my-custom-position that we can use on the position-try shorthand property. In this case, @position-try can replace the flip-inline value since it is the equivalent of top left:
@position-try --my-custom-position { position-area: top left; width: 50px; } .target { position: absolute; position-area: top right; position-try: most-width, --my-custom-position, bottom left; width: 100px; }This way, the .target element’s width is re-sized from 100px to 50px when it attempts to re-position itself to the anchor’s top-right edge. That’s a nice bit of flexibility that gives us a better chance to make things fit together in any layout.
Anchor functions anchor()You might think of the CSS anchor() function as a shortcut for attaching a target element to an anchor element — specify the anchor, the side we want to attach to, and how large we want the target to be in one fell swoop. But, as we’ll see, the function also opens up the possibility of attaching one target element to multiple anchor elements.
This is the function’s formal syntax, which takes up to three arguments:
anchor( <anchor-element>? && <anchor-side>, <length-percentage>? )So, we’re identifying an anchor element, saying which side we want the target to be positioned on, and how big we want it to be. It’s worth noting that anchor() can only be declared on inset-related properties (e.g. top, left, inset-block-end, etc.)
.target { top: anchor(--my-anchor bottom); left: anchor(--my-anchor end, 50%); }Let’s break down the function’s arguments.
<anchor-element>This argument specifies which anchor element we want to attach the target to. We can supply it with either the anchor’s name (see “Attaching targets to anchors”).
We also have the choice of not supplying an anchor at all. In that case, the target element uses an implicit anchor element defined in position-anchor. If there isn’t an implicit anchor, the function resolves to its fallback. Otherwise, it is invalid and ignored.
<anchor-side>This argument sets which side of the anchor we want to position the target element to, e.g. the anchor’s top, left, bottom, right, etc.
But we have more options than that, including logical side keywords (inside, outside), logical direction arguments relative to the user’s writing mode (start, end, self-start, self-end) and, of course, center.
- <anchor-side>: Resolves to the <length> of the corresponding side of the anchor element. It has physical arguments (top, left, bottom right), logical side arguments (inside, outside), logical direction arguments relative to the user’s writing mode (start, end, self-start, self-end) and the center argument.
- <percentage>: Refers to the position between the start (0%) and end (100%). Values below 0% and above 100% are allowed.
This argument is totally optional, so you can leave it out if you’d like. Otherwise, use it as a way of re-sizing the target elemenrt whenever it doesn’t have a valid anchor or position. It positions the target to a fixed <length> or <percentage> relative to its containing block.
Let’s look at examples using different types of arguments because they all do something a little different.
Using physical argumentsPhysical arguments (top, right, bottom, left) can be used to position the target regardless of the user’s writing mode. For example, we can position the right and bottom inset properties of the target at the anchor(top) and anchor(left) sides of the anchor, effectively positioning the target at the anchor’s top-left corner:
.target { bottom: anchor(top); right: anchor(left); } Using logical side keywordsLogical side arguments (i.e., inside, outside), are dependent on the inset property they are in. The inside argument will choose the same side as its inset property, while the outside argument will choose the opposite. For example:
.target { left: anchor(outside); /* is the same as */ left: anchor(right); top: anchor(inside); /* is the same as */ top: anchor(top); } Using logical directionsLogical direction arguments are dependent on two factors:
- The user’s writing mode: they can follow the writing mode of the containing block (start, end) or the target’s own writing mode (self-start, self-end).
- The inset property they are used in: they will choose the same axis of their inset property.
So for example, using physical inset properties in a left-to-right horizontal writing would look like this:
.target { left: anchor(start); /* is the same as */ left: anchor(left); top: anchor(end); /* is the same as */ top: anchor(bottom); }In a right-to-left writing mode, we’d do this:
.target { left: anchor(start); /* is the same as */ left: anchor(right); top: anchor(end); /* is the same as */ top: anchor(bottom); }That can quickly get confusing, so we should also use logical arguments with logical inset properties so the writing mode is respected in the first place:
.target { inset-inline-start: anchor(end); inset-block-start: anchor(end); } Using percentage valuesPercentages can be used to position the target from any point between the start (0%) and end (100% ) sides. Since percentages are relative to the user writing mode, is preferable to use them with logical inset properties.
.target { inset-inline-start: anchor(100%); /* is the same as */ inset-inline-start: anchor(end); inset-block-end: anchor(0%); /* is the same as */ inset-block-end: anchor(start); }Values smaller than 0% and bigger than 100% are accepted, so -100% will move the target towards the start and 200% towards the end.
.target { inset-inline-start: anchor(200%); inset-block-end: anchor(-100%); } Using the center keywordThe center argument is equivalent to 50%. You could say that it’s “immune” to direction, so there is no problem if we use it with physical or logical inset properties.
.target { position: absolute; position-anchor: --my-anchor; left: anchor(center); bottom: anchor(top); } anchor-size()The anchor-size() function is unique in that it sizes the target element relative to the size of the anchor element. This can be super useful for ensuring a target scales in size with its anchor, particularly in responsive designs where elements tend to get shifted, re-sized, or obscured from overflowing a container.
The function takes an anchor’s side and resolves to its <length>, essentially returning the anchor’s width, height, inline-size or block-size.
anchor-size( [ <anchor-element> || <anchor-size> ]? , <length-percentage>? )Here are the arguments that can be used in the anchor-size() function:
- <anchor-size>: Refers to the side of the anchor element.
- <length-percentage>: This optional argument can be used as a fallback whenever the target doesn’t have a valid anchor or size. It returns a fixed <length> or <percentage> relative to its containing block.
And we can declare the function on the target element’s width and height properties to size it with the anchor — or both at the same time!
.target { width: anchor-size(width, 20%); /* uses default anchor */` height: anchor-size(--other-anchor inline-size, 100px); } Multiple anchorsWe learned about the anchor() function in the last section. One of the function’s quirks is that we can only declare it on inset-based properties, and all of the examples we saw show that. That might sound like a constraint of working with the function, but it’s actually what gives anchor() a superpower that anchor positioning properties don’t: we can declare it on more than one inset-based property at a time. As a result, we can set the function multiple anchors on the same target element!
Here’s one of the first examples of the anchor() function we looked at in the last section:
.target { top: anchor(--my-anchor bottom); left: anchor(--my-anchor end, 50%); }We’re declaring the same anchor element named --my-anchor on both the top and left inset properties. That doesn’t have to be the case. Instead, we can attach the target element to multiple anchor elements.
.anchor-1 { anchor-name: --anchor-1; } .anchor-2 { anchor-name: --anchor-2; } .anchor-3 { anchor-name: --anchor-3; } .anchor-4 { anchor-name: --anchor-4; } .target { position: absolute; inset-block-start: anchor(--anchor-1); inset-inline-end: anchor(--anchor-2); inset-block-end: anchor(--anchor-3); inset-inline-start: anchor(--anchor-4); }Or, perhaps more succintly:
.anchor-1 { anchor-name: --anchor-1; } .anchor-2 { anchor-name: --anchor-2; } .anchor-3 { anchor-name: --anchor-3; } .anchor-4 { anchor-name: --anchor-4; } .target { position: absolute; inset: anchor(--anchor-1) anchor(--anchor-2) anchor(--anchor-3) anchor(--anchor-4); }The following demo shows a target element attached to two <textarea> elements that are registered anchors. A <textarea> allows you to click and drag it to change its dimensions. The two of them are absolutely positioned in opposite corners of the page. If we attach the target to each anchor, we can create an effect where resizing the anchors stretches the target all over the place almost like a tug-o-war between the two anchors.
CodePen Embed FallbackThe demo is only supported in Chrome at the time we’re writing this guide, so let’s drop in a video so you can see how it works.
AccessibilityThe most straightforward use case for anchor positioning is for making tooltips, info boxes, and popovers, but it can also be used for decorative stuff. That means anchor positioning doesn’t have to establish a semantic relationship between the anchor and target elements. You can probably spot the issue right away: non-visual devices, like screen readers, are left in the dark about how to interpret two seemingly unrelated elements.
As an example, let’s say we have an element called .tooltip that we’ve set up as a target element anchored to another element called .anchor.
<div class="anchor">anchor</div> <div class="toolip">toolip</div> .anchor { anchor-name: --my-anchor; } .toolip { position: absolute; position-anchor: --my-anchor; position-area: top; }We need to set up a connection between the two elements in the DOM so that they share a context that assistive technologies can interpret and understand. The general rule of thumb for using ARIA attributes to describe elements is generally: don’t do it. Or at least avoid doing it unless you have no other semantic way of doing it.
This is one of those cases where it makes sense to reach for ARIA atributes. Before we do anything else, a screen reader currently sees the two elements next to one another without any remarking relationship. That’s a bummer for accessibility, but we can easily fix it using the corresponding ARIA attribute:
<div class="anchor" aria-describedby="tooltipInfo">anchor</div> <div class="toolip" role="tooltip" id="tooltipInfo">toolip</div>And now they are both visually and semantically linked together! If you’re new to ARIA attributes, you ought to check out Adam Silver’s “Why, How, and When to Use Semantic HTML and ARIA” for a great introduction.
Browser supportThis browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
DesktopChromeFirefoxIEEdgeSafari125NoNo125NoMobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari129No129No Spec changesCSS Anchor Positioning has undergone several changes since it was introduced as an Editor’s Draft. The Chrome browser team was quick to hop on board and implement anchor positioning even though the feature was still being defined. That’s caused confusion because Chromium-based browsers implemented some pieces of anchor positioning while the specification was being actively edited.
We are going to outline specific cases for you where browsers had to update their implementations in response to spec changes. It’s a bit confusing, but as of Chrome 129+, this is the stuff that was shipped but changed:
position-areaThe inset-area property was renamed to position-area (#10209), but it will be supported until Chrome 131.
.target { /* from */ inset-area: top right; /* to */ position-area: top right; } position-try-fallbacksThe position-try-options was renamed to position-try-fallbacks (#10395).
.target { /* from */ position-try-options: flip-block, --smaller-target; /* to */ position-try-fallbacks: flip-block, --smaller-target; } inset-area()The inset-area() wrapper function doesn’t exist anymore for the position-try-fallbacks (#10320), you can just write the values without the wrapper:
.target { /* from */ position-try-options: inset-area(top left); /* to */ position-try-fallbacks: top left; } anchor(center)In the beginning, if we wanted to center a target from the center, we would have to write this convoluted syntax:
.target { --center: anchor(--x 50%); --half-distance: min(abs(0% - var(--center)), abs(100% - var(--center))); left: calc(var(--center) - var(--half-distance)); right: calc(var(--center) - var(--half-distance)); }The CWSSG working group resolved (#8979) to add the anchor(center) argument to prevent us from having to do all that mental juggling:
.target { left: anchor(center); } Known bugsYes, there are some bugs with CSS Anchor Positioning, at least at the time this guide is being written. For example, the specification says that if an element doesn’t have a default anchor element, then the position-area does nothing. This is a known issue (#10500), but it’s still possible to replicate.
So, the following code…
.container { position: relative; } .element { position: absolute; position-area: center; margin: auto; }…will center the .element inside its container, at least in Chrome:
CodePen Embed FallbackCredit to Afif13 for that great demo!
Another example involves the position-visibility property. If your anchor element is out of sight or off-screen, you typically want the target element to be hidden as well. The specification says that property’s the default value is anchors-visible, but browsers default to always instead.
The current implemenation in Chrome isn’t reflecting the spec; it indeed is using always as the initial value. But the spec is intentional: if your anchor is off-screen or otherwise scrolled off, you usually want it to hide. (#10425)
Almanac references Anchor position properties Almanac on Sep 17, 2024 anchor-name .anchor { anchor-name: --my-anchor; } anchor positioning Geoff Graham Almanac on Sep 12, 2024 position-anchor .target { position-anchor: --my-anchor; } anchor positioning Juan Diego Rodríguez Almanac on Sep 8, 2024 position-area .target { position-area: bottom end; } anchor positioning Juan Diego Rodríguez Almanac on Sep 14, 2024 position-try-fallbacks .target { position-try-fallbacks: flip-inline, bottom left; } anchor positioning Juan Diego Rodríguez Almanac on Sep 8, 2024 position-try-order .element { position-try-order: most-width; } anchor positioning Juan Diego Rodríguez Almanac on Sep 8, 2024 position-visibility .target { position-visibility: no-overflow; } anchor positioning Juan Diego Rodríguez Anchor position functions Almanac on Sep 14, 2024 anchor() .target { top: anchor(--my-anchor bottom); } anchor positioning Juan Diego Rodríguez Almanac on Sep 18, 2024 anchor-size() .target { width: anchor-size(width); } anchor positioning Juan Diego Rodríguez Anchor position at-rules Almanac on Sep 28, 2024 @position-try @position-try --my-position { position-area: top left; } anchor positioning Juan Diego Rodríguez Further reading- “CSS Anchor Positioning” (CSSWG)
- “Using CSS anchor positioning” (MDN)
- “Introducing the CSS anchor positioning API” (Una Kravets)
CSS Anchor Positioning Guide originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSS Masonry & CSS Grid
An approach for creating masonry layouts in vanilla CSS is one of those “holy grail” aspirations. I actually tend to plop masonry and the classic “Holy Grail” layout in the same general era of web design. They’re different types of layouts, of course, but the Holy Grail was a done deal when we got CSS Grid.
That leaves masonry as perhaps the last standing layout from the CSS 3 era that is left without a baked-in solution. I might argue that masonry is no longer en vogue so to speak, but there clearly are use cases for packing items with varying sizes into columns based on available space. And masonry is still very much in the wild.
Steam is picking up on a formal solution. We even have a CSSWG draft specification for it. But notice how the draft breaks things out.
Grid-integrated syntax? Grid-independent syntax? We’ve done gone and multiplied CSS!
That’s the context for this batch of notes. There are two competing proposals for CSS masonry at the time of writing and many opinions are flying around advocating one or the other. I have personal thoughts on it, but that’s not important. I’ll be happy with whatever the consensus happens to be. Both proposals have merits and come with potential challenges — it’s a matter of what you prioritize which, in this case, I believe is a choice between leveraging existing CSS layout features and the ergonomics of a fresh new approach.
But let’s get to some notes from discussions that are already happening to help get a clearer picture of things!
What is masonry layout?Think of it like erecting a wall of stones or bricks.
The sizes of the bricks and stones don’t matter — the column (or less commonly a row) is the boss of sizing things. Pack as many stones or bricks in the nearest column and then those adapt to the column’s width. Or more concisely, we’re laying out unevenly sized items in a column such that there aren’t uneven gaps between them.
Examples, please?Here’s perhaps the most widely seen example in a CodePen, courtesy of Dave DeSandro, using his Masonry.js tool:
CodePen Embed FallbackI use this example because, if I remember correctly, Masonry.js was what stoked the masonry trend in, like 2010 or something. Dave implemented it on Beyoncé’s website which certainly gave masonry a highly visible profile. Sometimes you might hear masonry called a “Pinterest-style” layout because, well, that’s been the site’s signature design — perhaps even its brand — since day one.
Here’s a faux example Jhey put together using flexbox:
CodePen Embed FallbackChris also rounded up a bunch of other workarounds in 2019 that get us somewhat there, under ideal conditions. But none of these are based on standardized approaches or features. I mean, columns and flexbox are specced but weren’t designed with masonry in mind. But with masonry having a long track record of being used, it most certainly deserves a place in the CSS specs.
There are two competing proposalsThis isn’t exactly news. In fact, we can get earlier whiffs of this looking back to 2020. Rachel Andrew introduced the concept of making masonry a sub-feature of grid in a Smashing Magazine article.
Let’s fast-forward to 2022. We had an editor’s draft for CSS Masonry baked into the CSS Grid Layout Module 3 specification. Jenn Simmons motioned for the CSSWG to move it forward to be a first public working draft. Five days later, Chromium engineer Ian Kilpatrick raised two concerns about moving things forward as part of the CSS Grid Layout module, the first being related to sizing column tracks and grid’s layout algorithm:
Grid works by placing everything in the grid ahead of time, then sizing the rows/columns to fit the items. Masonry fundamentally doesn’t work this way as you need to size the rows/columns ahead of time – then place items within those rows/columns.
As a result the way the current specification re-uses the grid sizing logic leads to poor results when intrinsically sizing tracks, and if the grid is intrinsically-sized itself (e.g. if its within a grid/flex/table, etc).
Good point! Grid places grid items in advance ahead of sizing them to fit into the available space. Again, it’s the column’s size that bosses things around in masonry. It logically follows that we would need to declare masonry and configure the column track sizes in advance to place things according to space. The other concern concerns accessibility as far as visual and reading order.
That stopped Jenn’s motion for first public working draft status dead in its tracks in early 2023. If we fast-forward to July of this year, we get Ian’s points for an alternative path forward for masonry. That garnered support from all sorts of CSS heavyweights, including Rachel Andrew who authored the CSS Grid specification.
And, just a mere three weeks ago from today, fantasai shared a draft for an alternate proposal put together with Tab Atkins. This proposal, you’ll see, is specific to masonry as its own module.
And thus we have two competing proposals to solve masonry in CSS.
The case for merging masonry and gridRounding up comments from GitHub tickets and blog posts…
Flexbox is really designed for putting things into a line and distributing spare space. So that initial behaviour of putting all your things in a row is a great starting point for whatever you might want to do. It may be all you need to do. It’s not difficult as a teacher to then unpack how to add space inside or outside items, align them, or make it a column rather than a row. Step by step, from the defaults.
I want to be able to take the same approach with display: masonry.
[…]
We can’t do that as easily with grid, because of the pre-existing initial values. The good defaults for grid don’t work as well for masonry. Currently you’d need to:
- Add display: grid, to get a single column grid layout.
- Add grid-template-columns: <track-listing>, and at the moment there’s no way to auto-fill auto sized tracks so you’ll need to decide on how many. Using grid-template-columns: repeat(3, auto), for example.
- Add grid-template-rows: masonry.
- Want to define rows instead? Switch the masonry value to apply to grid-template-columns and now define your rows. Once again, you have to explicitly define rows.
For what it’s worth, Rachel has been waving this flag since at least 2020. The ergonomics of display: masonry with default configurations that solve baseline functionality are clear and compelling. The default behavior oughta match the feature’s purpose and grid just ain’t a great set of default configurations to jump into a masonry layout. Rachel’s point is that teaching and learning grid to get to understand masonry behavior unnecessarily lumps two different formatting contexts into one, which is a certain path to confusion. I find it tough to refute this, as I also come at this from a teaching perspective. Seen this way, we might say that merging features is another lost entry point into front-end development.
In recent years, the two primary methods we’ve used to pull off masonry layouts are:
- Flexbox for consistent row sizes. We adjust the flex-basis based on the item’s expected percentage of the total row width.
- Grid for consistent column sizes. We set the row span based on the expected aspect ratio of the content, either server-side for imagery or client-side for dynamic content.
What I’ve personally observed is:
- Neither feels more intuitive than the other as a starting point for masonry. So it feels a little itchy to single out Grid as a foundation.
- While there is friction when teaching folks when to use a Flexbox versus a Grid, it’s a much bigger leap for contributors to wrap their heads around properties that significantly change behavior (such as flex-wrap or grid-auto-flow: dense).
It’s true! If I had to single out either flexbox or grid as the starting poit for masonry (and I doubt I would either way), I might lean flexbox purely for the default behavior of aligning flexible items in a column.
The syntax and semantics of the CSS that will drive masonry layout is a concern that is separate from the actual layout mechanics itself, which internally in implementation by user agents can still re-use parts of the existing mechanics for grids, including subgrids. For cases where masonry is nested inside grid, or grid inside masonry, the relationship between the two can be made explicit.
@jgotten, commenting on GitHub Issue #9041Rachel again, this time speaking on behalf of the Chrome team:
There are two related reasons why we feel that masonry is better defined outside of grid layout—the potential of layout performance issues, and the fact that both masonry and grid have features that make sense in one layout method but not the other.
The case for keeping masonry separate from gridOne of the key benefits of integrating masonry into the grid layout (as in CASE 2) is the ability to leverage existing grid features, such as subgrids. Subgrids allow for cohesive designs among child elements within a grid, something highly desirable in many masonry layouts as well. Additionally, I believe that future enhancements to the grid layout will also be beneficial for masonry, making their integration even more valuable. By treating masonry as an extension of the grid layout, developers would be able to start using it immediately, without needing to learn a completely new system.
Kokomi, commenting on GitHub Issue #9041It really would be a shame if keeping masonry separate from grid prevents masonry from being as powerful as it could be with access to grid’s feature set:
I think the arguments for a separate display: masonry focus too much on the potential simplicity at the expense of functionality. Excluding Grid’s powerful features would hinder developers who want or need more than basic layouts. Plus, introducing another display type could lead to confusion and fragmentation in the layout ecosystem.
Angel Ponce, commenting on GitHub Issue #9041Rachel counters that, though.
I want express my strong support for adding masonry to display:grid. The fact that it gracefully degrades to a traditional grid is a huge benefit IMO. But also, masonry layout is already possible (with some constraints) in Grid layout today!
Naman Goel, Angel Ponce, commenting on GitHub Issue #9041Chris mildly voiced interest in merging the two in 2020 before the debate got larger and more heated. Not exactly a ringing endorsement, but rather an acknowledgment that it could make sense:
I like the grid-template-rows: masonry; syntax because I think it clearly communicates: “You aren’t setting these rows. In fact, there aren’t even really rows at all anymore, we’ll take care of that.” Which I guess means there are no rows to inherit in subgrid, which also makes sense.
Where we at?Collecting feedback. Rachel, Ian, and Tab published a joint call for folks like you and me to add our thoughts to the bag. That was eight days ago as of this writing. Not only is it a call to action, but it’s also an excellent overview of the two competing ideas and considerations for each one. You’ll want to add your feedback to GitHub Issue #9041.
CSS Masonry & CSS Grid originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Slide Through Unlimited Dimensions With CSS Scroll Timelines
The creator of CSS has said he originally envisaged CSS as the main web technology to control behavior on web pages, with scripting as a fallback when things weren’t possible declaratively in CSS. The rationale for a CSS-first approach was that “scripting is programming and programming is hard.” Since introducing the :hover pseudo-class, CSS has been standardizing patterns developers create in JavaScript and “harvesting” them into CSS standards. When you think about it like that, it’s almost as if JavaScript is the hack and CSS is the official way.
We can, therefore, feel less dirty implementing script-like behavior with CSS, and we shouldn’t be surprised that something like the new scroll-timeline feature has appeared with pretty good browser support. Too many developers implemented clever parallax scrolling websites, which has summoned the CSS feature genie we cannot put back in its bottle. If you don’t want janky main-thread animations for your next parallax-scrolling website, you must now come to the dark side of hacking CSS. Just kidding, there is also a new JavaScript API for scroll-linked animations if imperative programming better fits your use case.
Migrating a JavaScript sample to CSSIt was satisfyingly simple to fork Chris Coyier’s pre-scroll-timeline example of a scroll-linked animation by replacing the CSS Chris was using to control the animations with just one line of CSS and completely deleting the JavaScript!
body, .progress, .cube { animation-timeline: scroll(); } CodePen Embed FallbackUsing the scroll() function without parameters sets up an “anonymous scroll progress timeline” meaning the browser will base the animation on the nearest ancestor that can scroll vertically if our writing mode is English. Unfortunately, it seems we can only choose to animate based on scrolling along the x or y-axis of a particular element but not both, which would be useful. Being a function, we can pass parameters to scroll(), which provides more control over how we want scrolling to run our animation.
Experimenting with multiple dimensionsEven better is the scroll-scope property. Applying that to a container element means we can animate properties on any chosen ancestor element based on any scrollable element that has the same assigned scope. That got me thinking… Since CSS Houdini lets us register animation-friendly, inheritable properties in CSS, we can combine animations on the same element based on multiple scrollable areas on the page. That opens the door for interesting instructional design possibilities such as my experiment below.
CodePen Embed FallbackScrolling the horizontal narrative on the light green card rotates the 3D NES console horizontally and scrolling the vertical narrative on the dark green card rotates the NES console vertically. In my previous article, I noted that my past CSS hacks have always boiled down to hiding and showing finite possibilities using CSS. What interests me about this scroll-based experiment is the combinatorial explosion of combined vertical and horizontal rotations. Animation timelines provide an interactivity in pure CSS that hasn’t been possible in the past.
The implementation details are less important than the timeline-scope usage and the custom properties. We register two custom angle properties:
@property --my-y-angle { syntax: "<angle>"; inherits: true; initial-value: 0deg; } @property --my-x-angle { syntax: "<angle>"; inherits: true; initial-value: -35deg; }Then, we “borrow” the NES 3D model from the samples in Julian Garner’s amazing CSS 3D modeling app. We update the .scene class for the 3D to base the rotation on our new variables like this:
.scene { transform: rotateY(var(--my-y-angle)) rotateX(var(--my-x-angle)); }Next, we give the <body> element a timeline-scope with two custom-named scopes.
body { timeline-scope: --myScroller,--myScroller2; }I haven’t seen anything officially documented about passing in multiple scopes, but it does work in Google Chrome and Edge. If it’s not a formally supported feature, I hope it will become part of the standard because it is ridiculously handy.
Next, we define the named timelines for the two scrollable cards and the axes we want to trigger our animations.
.card:first-child { scroll-timeline-axis: x; scroll-timeline-name: --myScroller; } .card:nth-child(2) { scroll-timeline-axis: y; scroll-timeline-name: --myScroller2; }And add the animations to the scene:
.scene { animation: rotateHorizontal,rotateVertical; animation-timeline: --myScroller,--myScroller2; } @keyframes rotateHorizontal { to { --my-y-angle: 360deg; } } @keyframes rotateVertical { to { --my-x-angle: 360deg; } }Since the 3D model inherits the x and y angles from the document body, scrolling the cards now rotates the model in combinations of vertical and horizontal angle changes.
User-controlled animations beyond scrollbarsWhen you think about it, this behavior isn’t just useful for scroll-driven animations. In the above experiment, we are using the scrollable areas more like sliders that control the properties of our 3D model. After getting it working, I went for a walk and was daydreaming about how cool it would be if actual range inputs could control animation timelines. Then I found out they can! At least in Chrome. Pure CSS CMS anyone?
While we’re commandeering 3D models from Julian Garner, let’s see if we can use range inputs to control his X-wing model.
CodePen Embed FallbackIt’s mind-boggling that we can achieve this with just CSS, and we could do it with an arbitrary number of properties. It doesn’t go far enough for me. I would love to see other input controls that can manipulate animation timelines. Imagine text fields progressing animations as you fill them out, or buttons able to play or reverse animations. The latter can be somewhat achieved by combining the :active pseudo-class with the animation-play-state property. But in my experience when you try to use that to animate multiple custom properties, the browser can get confused. By contrast, animation timelines have been implemented with this use case in mind and therefore work smoothly and exactly as I expected.
I’m not the only one who has noticed the potential for hacking this emergent CSS feature. Someone has already implemented this clever Doom clone by combining scroll-timeline with checkbox hacks. The problem I have is it still doesn’t go far enough. We have enough in Chrome to implement avatar builders using scrollbars and range inputs as game controls. I am excited to experiment with unpredictable, sophisticated experiences that are unprecedented in the era before the scroll-timeline feature. After all, if you had to explain the definition of a video game to an alien, wouldn’t you say it is just a hyper-interactive animation?
Slide Through Unlimited Dimensions With CSS Scroll Timelines originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Aggregating my distributed self
Miriam Suzanne’s in the middle of a redesign of her personal website. It began in August 2022. She’s made an entire series out of the work that’s worth your time, but I wanted to call out the fifth and latest installment because she presents a problem that I think we can all relate to:
But the walls got in my way. Instead of minimal renovation, I got just far enough to live with it and then started a brand new Eleventy repo.
The plan was to prototype […] and bring back well-formed solutions. To echo Dave Rupert, prototyping is useful. It’s easier to play with new ideas when you’re not carrying a decade of content and old code along with you.
But prototyping evolved into what I would call tinkering (complimentary). Maybe I mean procrastinating (also complimentary), but it’s a wandering process that also helps me better understand what I want from a website. I might not make visible progress over two years, but I start to form a point of view […]. Keeping things easy is always where things get complicated. And it brings me back to where my redesign started – a desire to clarify the information architecture. Not only for visitors, but for myself.
Don’t even tell me you’ve never been there! Jim Neilsen blogged along similar lines this summer. You get a stroke of inspiration that’s the kernel of some idea that motivates you to start, you know, working on it. There’s no real plan, perhaps. The idea and inspiration are more than enough to get you going… that is until you hit a snag. And what I appreciate about Miriam’s post is that she’s calling out content as the snag. Well, not so much a snag as a return to the founding principle for the redesign: a refined content architecture.
- Sometimes I do events where I speak, or teach a workshop, or perform. Events happen at a time and place.
- Sometimes I create artifacts like a book or an album, a website, or specification. Artifacts often have a home URL. They might have a launch date, but they are not date-specific.
- Some of my projects are other channels with their own feeds, their own events and artifacts.
- Those channels are often maintained by an organization that I work with long-term. A band, a web agency, a performance company, etc.
These boundaries aren’t always clean. A post that remains relevant could be considered an artifact. Events can generate artifacts, and vice versa. An entire organization might exist to curate a single channel.
So, Miriam’s done poking at visual prototypes and ready to pour the filling into the pie crust. I relate with this having recently futzed with the content architecure of this site. I find it tough to start with a solidified design before I know what content is going into it. But I also find it tough to work with no shape at all. In my case, CSS-Tricks has a well-established design that’s evolved, mostly outside of me. I love the design but it’s an inherited one and I’m integrating content around it. Design is the constraint. If I had the luxury of stripping the building to the studs, I might take a different approach because then I could “paint” around it. Content would be the constraint.
It’s yet another version of the Chicken-Egg dilemma. I still think of the (capital-W) Web as a content medium at least in a UA style sense in that it’s the default. It’s more than that, of course. I’m a content designer at heart (and trade) but I’m hesitant to cry “content is king” which reminded me of something I wrote for an end-of-year series we did here answering the question: What is one thing people can do to make their website better? My answer: Read your website.
We start to see the power of content when we open up our understanding of what it is, what it does, and where it’s used. That might make content one of the most extensible problem-solving tools in your metaphorical shed—it makes sites more accessible, extracts Google-juicing superpowers, converts sales, and creates pathways for users to accomplish what they need to do.
And as far as prioritizing content or design, or…?
The two work hand-in-hand. I’d even go so far as to say that a lot of design is about enhancing what is communicated on a page. There is no upstaging one or the other. Think of content and design as supporting one another, where the sum of both creates a compelling call-to-action, long-form post, hero banner, and so on. We often think of patterns in a design system as a collection of components that are stitched together to create something new. Pairing content and design works much the same way.
I’d forgotten those words, so I appreciate Miriam giving me a reason to revisit them. We all need to be recalibrated every so often — swap out air filters, top off the fluids, and rotate the ol’ tires. And an old dog like me needs it a little more often. I spent a few more minutes in that end-of-year series and found a few other choice quotes about the content-design continuum that may serve as inspiration for you, me, or maybe even Miriam as she continues the process of aggragating her distributed self.
This sounds serious, but don’t worry — the site’s purpose is key. If you’re building a personal portfolio, go wild! However, if someone’s trying to file a tax return, whimsical loading animations aren’t likely to be well-received. On the other hand, an animated progress bar could be a nice touch while providing visual feedback on the user’s action.
Cassie Evans, “Empathetic Animation”Remember, the web is an interactive platform — take advantage of that, where appropriate (less is more, accessibility is integral, and you need to know your audience). Whether that’s scrollytelling, captioned video, and heck, maybe for your audience, now’s the time to start looking into AR/VR! Who knows. Sometimes you just need to try stuff out and see what sticks. Just be careful. Experimentation is great, but we need to make sure we’re bringing everyone along for the ride.
Mel Choyce, “Show, Don’t Tell”Your personal site is a statement of who you are and what you want to do. If you showcase your favorite type of work, you’ll get more requests for similar projects or jobs — feeding back into a virtuous cycle of doing more of what you love.
Amelia Wattenberger, “Exactly What You Want”And one of my favorites:
But the prime reason to have a personal website is in the name: it is your personalhome on the web. Since its early days, the web has been about sharing information and freedom of expression. Personal websites still deliver on that promise. Nowhere else do you have that much freedom to create and share your work and to tell your personal story. It is your chance to show what you stand for, to be different, and to be specific. Your site lets you be uniquely you and it can be whatever you imagine it to be.
So if you have a personal site, make sure to put in the work and attention to make it truly yours. Make it personal. Fine-tune the typography, add a theme switcher, or incorporate other quirky little details that add personality. As Sarah Drasner writes, you can feel it if a site is done with care and excitement. Those are the sites that are a joy to visit and will be remembered.
Matthias Ott, “Make it Personal”That last one has the added perk of reminding me how incredibly great Sarah Drasner is.
Aggregating my distributed self originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
How to Make a “Scroll to Select” Form Control
The <select> element is a fairly straightforward concept: focus on it to reveal a set of <option>s that can be selected as the input’s value. That’s a great pattern and I’m not suggesting we change it. That said, I do enjoy poking at things and found an interesting way to turn a <select> into a dial of sorts — where options are selected by scrolling them into position, not totally unlike a combination lock or iOS date pickers. Anyone who’s expanded a <select> for selecting a country knows how painfully long lists can be and this could be one way to prevent that.
Here’s what I’m talking about:
CodePen Embed FallbackIt’s fairly common knowledge that styling <select> in CSS is not the easiest thing in the world. But here’s the trick: we’re not working with <select> at all. No, we’re not going to do anything like building our own <select> by jamming a bunch of JavaScript into a <div>. We’re still working with semantic form controls, only it’s radio buttons.
<section class=scroll-container> <label for="madrid" class="scroll-item"> Madrid <abbr>MAD</abbr> <input id="madrid" type="radio" name="items"> </label> <label for="malta" class="scroll-item"> Malta <abbr>MLA</abbr> <input id="malta" type="radio" name="items"> </label> <!-- etc. --> </section>What we need is to style the list of selectable controls where we are capable of managing their sizes and spacing in CSS. I’ve gone with a group of labels with nested radio boxes as far as the markup goes. The exact styling is totally up to you, of course, but you can use these base styles I wrote up if you want a starting point.
.scroll-container { /* SIZING & LAYOUT */ --itemHeight: 60px; --itemGap: 10px; --containerHeight: calc((var(--itemHeight) * 7) + (var(--itemGap) * 6)); width: 400px; height: var(--containerHeight); align-items: center; row-gap: var(--itemGap); border-radius: 4px; /* PAINT */ --topBit: calc((var(--containerHeight) - var(--itemHeight))/2); --footBit: calc((var(--containerHeight) + var(--itemHeight))/2); background: linear-gradient( rgb(254 251 240), rgb(254 251 240) var(--topBit), rgb(229 50 34 / .5) var(--topBit), rgb(229 50 34 / .5) var(--footBit), rgb(254 251 240) var(--footBit)); box-shadow: 0 0 10px #eee; }A couple of details on this:
- --itemHeight is the height of each item in the list.
- --itemGap is meant to be the space between two items.
- The --containerHeight variable is the .scroll-container’s height. It’s the sum of the item sizes and the gaps between them, ensuring that we display, at maximum, seven items at once. (An odd number of items gives us a nice balance where the selected item is directly in the vertical center of the list).
- The background is a striped gradient that highlights the middle area, i.e., the location of the currently selected item.
- The --topBit and –-footBit variables are color stops that visually paint in the middle area (which is orange in the demo) to represent the currently selected item.
I’ll arrange the controls in a vertical column with flexbox declared on the .scroll-container:
.scroll-container { display: flex; flex-direction: column; /* rest of styles */ }With layout work done, we can focus on the scrolling part of this. If you haven’t worked with CSS Scroll Snapping before, it’s a convenient way to direct a container’s scrolling behavior. For example, we can tell the .scroll-container that we want to enable scrolling in the vertical direction. That way, it’s possible to scroll to the rest of the items that are not in view.
.scroll-container { overflow-y: scroll; /* rest of styles */ }Next, we reach for the scroll-snap-style property that can be used to tell the .scroll-container that we want scrolling to stop on an item — not near an item, but directly on it.
.scroll-container { overflow-y: scroll; scroll-snap-type: y mandatory; /* rest of styles */ }Now items “snap” onto an item instead of allowing a scroll to end wherever it wants. One more little detail I like to include is overscroll-behavior, specifically along the y-axis as far as this demo goes:
.scroll-container { overflow-y: scroll; scroll-snap-type: y mandatory; overscroll-behavior-y: none; /* rest of styles */ }overscroll-behavior-y: none isn’t required to make this work, but when someone scrolls through the .scroll-container (along the y-axis), scrolling stops once the boundary is reached, and any further continued scrolling action will not trigger scrolling in any nearby scroll containers. Just a form of defensive CSS.
Time to move to the items inside the scroll container. But before we go there, here are some base styles for the items themselves that you can use as a starting point:
.scroll-item { /* SIZING & LAYOUT */ width: 90%; box-sizing: border-box; padding-inline: 20px; border-radius: inherit; /* PAINT & FONT */ background: linear-gradient(to right, rgb(242 194 66), rgb(235 122 51)); box-shadow: 0 0 4px rgb(235 122 51); font: 16pt/var(--itemHeight) system-ui; color: #fff; input { appearance: none; } abbr { float: right; } /* The airport code */ }As I mentioned earlier, the --itemHeight variable is setting as the size of each item and we’re declaring it on the flex property — flex: 0 0 var(--itemHeight). Margin is added before and after the first and last items, respectively, so that every item can reach the middle of the container through scrolling.
The scroll-snap-align property is there to give the .scroll-container a snap point for the items. A center alignment, for instance, snaps an item’s center (vertical center, in this case) with the .scroll-container‘s center (vertical center as well). Since the items are meant to be selected through scrolling alone pointer-events: none is added to prevent selection from clicks.
One last little styling detail is to set a new background on an item when it is in a :checked state:
.scroll-item { /* Same styles as before */ /* If input="radio" is :checked */ &:has(:checked) { background: rgb(229 50 34); } }But wait! You’re probably wondering how in the world an item can be :checked when we’re removing pointer-events. Good question! We’re all finished with styling, so let’s move on to figuring some way to “select” an item purely through scrolling. In other words, whatever item scrolls into view and “snaps” into the container’s vertical center needs to behave like a typical form control selection. Yes, we’ll need JavaScript for that.
let observer = new IntersectionObserver(entries => { entries.forEach(entry => { with(entry) if(isIntersecting) target.children[1].checked = true; }); }, { root: document.querySelector(`.scroll-container`), rootMargin: `-51% 0px -49% 0px` }); document.querySelectorAll(`.scroll-item`).forEach(item => observer.observe(item));The IntersectionObserver object is used to monitor (or “observe”) if and when an element (called a target) crosses through (or “intersects”) another element. That other element could be the viewport itself, but in this case, we’re observing the .scroll-container for when a .scroll-item intersects it. We’ve established the observed boundary with rootMargin:"-51% 0px -49% 0px".
A callback function is executed when that happens, and we can use that to apply changes to the target element, which is the currently selected .scroll-item. In our case, we want to select a .scroll-item that is at the halfway mark in the .scroll-container: target.children[1].checked = true.
That completes the code. Now, as we scroll through the items, whichever one snaps into the center position is the selected item. Here’s a look at the final demo again:
CodePen Embed FallbackLet’s say that, instead of selecting an item that snaps into the .scroll-container‘s vertical center, the selection point we need to watch is the top of the container. No worries! All we do is update the scroll-snap-align property value from center to start in the CSS and remove the :first-of-type‘s top margin. From there, it’s only a matter of updating the scroll container’s background gradient so that the color stops highlight the top instead of the center. Like this:
CodePen Embed FallbackAnd if one of the items has to be pre-selected when the page loads, we can get its position in JavaScript (getBoundingClientRect()) and use the scrollTo() method to scroll the container to where that specific item’s position is at the point of selection (which we’ll say is the center in keeping with our original demo). We’ll append a .selected class on that .scroll-item.
<section class="scroll-container"> <!-- more items --> <label class="scroll-items selected"> 2024 <input type=radio name=items /> </label> <!-- more items --> </section>Let’s select the .selected class, get its dimensions, and automatically scroll to it on page load:
let selected_item = (document.querySelector(".selected")).getBoundingClientRect(); let scroll_container = document.querySelector(".scroll-container"); scroll_container.scrollTo(0, selected_item.top - scroll_container.offsetHeight - selected_item.height);It’s a little tough to demo this in a typical CodePen embed, so here’s a live demo in a GitHub Page (source code). I’ll drop a video in as well:
That’s it! You can build up this control or use it as a starting point to experiment with different layouts, styles, animations, and such. It’s important the UX clearly conveys to the users how the selection is done and which item is currently selected. And if I was doing this in a production environment, I’d want to make sure there’s a good fallback experience for when JavaScript might be unavailable and that my markup performs well on a screen reader.
References and further reading- A Few Functional Uses for Intersection Observer to Know When an Element is in View (Preethi Sam)
- An Explanation of How the Intersection Observer Watches (Travis Almand)
- Practical CSS Scroll Snapping (Max Kohler)
- The Current State of Styling Selects in 2019 (Chris Coyier)
- CSS Flexbox Layout Guide (CSS-Tricks)
- CSS flex property (CSS-Tricks)
- CSS Scroll Snap Properties (MDN)
- scrollTo() (MDN)
How to Make a “Scroll to Select” Form Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #21
Quick Hit #21 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSSWG Minutes Telecon (2024-09-18)
For the past two months, all my livelihood has gone towards reading, researching, understanding, writing, and editing about Anchor Positioning, and with many Almanac entries published and a full Guide guide on the way, I thought I was ready to tie a bow on it all and call it done. I know that Anchor Positioning is still new and settling in. The speed at which it’s moved, though, is amazing. And there’s more and more coming from the CSSWG!
That all said, I was perusing the last CSSWG minutes telecon and knew I was in for more Anchor Positioning when I came to the following resolution:
Whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree) (Issue #10526: When does anchor-scope “match” a name?)
Resolutions aren’t part of the specification or anything, but the strongest of indications about where they’re headed. So, I thought this was a good opportunity not only to take a peek at what we might get in anchor-scope and touch on other interesting bits from the telecon.
Remember that you can subscribe and read the full minutes on W3C.org. :)
Full Transcription What’s anchor-scope?To register an anchor, we can give it a distinctive anchor-name and then absolutely positioned elements with a matching position-anchor are attached to it. Even though it may look like it, anchor-name doesn’t have to be unique — we may reuse an anchor element inside a component with the same anchor-name.
<ul> <li> <div class="anchor">Anchor 1</div> <div class="target">Target 1</div> </li> <li> <div class="anchor">Anchor 2</div> <div class="target">Target 2</div> </li> <li> <div class="anchor">Anchor 3</div> <div class="target">Target 3</div> </li> </ul>However, if we try to connect them with CSS,
.anchor { anchor-name: --my-anchor; } .target { position: absolute; position-anchor: --my-anchor; position-area: top right; }We get an unpleasant surprise where instead of each .anchor having their .target positioned at its top-right edge, they all pile up on the last .anchor instance. We can see it better by rotating each target a little. You’ll want to check out the next demo in Chrome 125+ to see the behavior:
CodePen Embed FallbackThe anchor-scope property should make an anchor element only discoverable by targets in their individual subtree. So, the prior example would be fixed in the future like this:
.anchor { anchor-name: --my-anchor; anchor-scope: --my-anchor; }This is fairly straightforward — anchor-scope makes the anchor element available only in that specific subtree. But then we have to ask another question: What should the anchor-scope own scope be? We can’t have an anchor-scope-scope property and then an anchor-scope-scope-scope and so on… so which behavior should it be?
This is what started the conversation, initially from a GitHub issue:
When an anchor-scope is specified with a <dashed-ident>, it scopes the name to that subtree when the anchor name is “matching”. The problem is that this matching can be interpreted in at least three ways: (Assuming that anchor-scope is a tree-scoped reference, which is also not clear in the spec):
- It matches by the ident part of the name only, ignoring any tree-scope that would be associated with the name, or
- It matches by exact match of the ident part and the associated tree-scope, or
- It matches by some mechanism similar to dereferencing of tree-scoped references, where it’s a match when the tree-scope of the anchor-scope-name is an inclusive ancestor of the tree-scope of the anchor query.
And then onto the CSSWG Minutes:
TabAtkins: In anchor positioning, anchor names and references are tree scoped. The anchor-scope property that scopes, does not say whether the names are tree scoped or not. Question to decide: should they be?
TabAtkins: I think the answer should be yes. If you have an anchor in a shadow tree with a part involved, then problems result if anchor scopes are not tree scoped. This is bad, so I think it should be tree scoped
<khush> sounds pretty reasonable
<fantasai> makes sense to me as far as I can understand it :)
This solution of the scope of scoping properties expanded towards View Transitions, which also rely on tree scoping to work:
khush: Thinking about this in the context of view transitions: in that API you give names and the tree scope has to be the same for them to match. There is another view transitions feature where I’m not sure if the spec says it’s tree scoped
khush: Want to make sure that feature is covered by the more general resolution
TabAtkins: Proposed more general resolution: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
So the scope of anchor-scope is tree-scoped. Say that five times fast!
RESOLVED: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
The next resolution was pretty straightforward. Besides allowing a <dashed-ident> that says that specific anchor is three-scoped, the anchor-scope property can take an all keyword, which means that all anchors are tree-scoped. So, the question was if all is also a tree-scoped value.
TabAtkins: anchor-scope, in addition to idents, can take the keyword ‘all‘, which scopes all names. Should this be a tree-scoped ‘all‘? (i.e. only applies to the current tree scope)
TabAtkins: Proposed resolution: the ‘all‘ keyword is also tree-scoped in the same way sgtm +1, again same pattern with view-transition-group
RESOLVED: the ‘all‘ keyword is tree-scoped
The conversation switched gears toward new properties coming in the CSS Scroll Snap Module Level 2 draft, which is all about changing the user’s initial scroll with CSS. Taking an example directly from the spec, say we have an image carousel:
<div class="carousel"> <img src="img1.jpg"> <img src="img2.jpg"> <img src="img3.jpg" class="origin"> <img src="img4.jpg"> <img src="img5.jpg"> </div>We could set the initial scroll to show another image by setting it’s scroll-start-target to auto:
.carousel { overflow-inline: auto; } .carousel .origin { scroll-start-target: auto; }As of right now, the only way to achieve this is using JavaScript to scroll an element into view:
document.querySelector(".origin").scrollIntoView({ behavior: "auto", block: "center", inline: "center" });The last example is probably a carousel that is only scrollable in the inline direction. Still, there are doubts as far when the container is scrollable in both the inline and block directions. As seen in the initial GitHub issue:
The scroll snap 2 spec says that when there are multiple elements that could be scroll-start-targets for a scroll container “user-agents should select the one which comes first in tree order“.
Selecting the first element in tree-order seems like a natural way to resolve competition between multiple targets which would be scrolled to in one particular axis but is perhaps not as flexible as might be needed for the 2d case where an author wants to scroll to one item in one axis and another item in the other axis.
And back to the CSSWG minutes:
DavidA: We have a property we’re adding called scroll-start-target that indicates if an element within a scroll container, then the scroll should start with that element onscreen. Question is what happens if there are multiple targets?
DavidA: Propose to do it in reverse-DOM order, this would result in the first one applied last and then be on screen. Also, should only change the scroll position if you have to.
After discussing why we have to define scroll-start-target when we have scroll-snap-align, the discussion went on the reverse-DOM order:
fantasai: There was a bunch of discussion about regular vs reverse-DOM order. Where did we end up and why?
flackr: Currently, we expect that it scrolls to the first item in DOM order. We probably want that to still happen. That is why the proposal is to scroll to each item in sequence in reverse-DOM order.
So we are coming in reverse to scroll the element, but only as required so the following elements are showing as much as possible:
flackr: There is also the issue of nearest…
fantasai: Can you explain nearest?
flackr: Same as scroll into view
fantasai: ?
flackr: This is needed with you scroll multiple things into view and want to find a good position (?)
fantasai: You scroll in reverse-DOM order…when you add the spec can you make it really clear that this is the end result of the algorithm?
flackr: Yes absolutely
fantasai: Otherwise it seems to make sense
And so it was resolved:
Proposed resolution 2: When scroll-start-target targets multiple elements, scroll to each in reverse DOM order with text to specify priority is the first item
Lastly, there was the debate about the text-underline-position, that when set to auto says, “The user agent may use any algorithm to determine the underline’s position; however it must be placed at or under the alphabetic baseline.” The discussion was about whether the auto value should automatically adjust the underlined position to match specific language rules, for example, at the right of the text for vertical writing modes, like Japanese and Mongolian.
fantasai: The initial value of text-underline-position is auto, which is defined as “find a good place to put the underline”.
Three options there: (1) under alphabetical baseline, (2) fully below text (good for lots-of-descenders cases), (3) for vertical text on the RHS
fantasai: auto value is defined in the spec about ‘how far down below the text’, but doesn’t say things about flipping. The current spec says “at or below”. In order to handle language-specific aspects, there is a default UA style sheet that for Chinese and Japanese and Korean there are differences for those languages. A couple of implementations do this
fantasai: Should we change the spec to mention these things?
fantasai: Or should we stick with the UA stylesheet approach?
The thing is that Chrome and Firefox already place the underline on the right in vertical Japanese when text-underline-position is auto.
CodePen Embed FallbackThe group was left with three options:
<fantasai> A) Keep spec as-is, update Gecko + Blink to match (using UA stylesheet for language switch)
<fantasai> B) Introduce auto to text-emphasis-position and use it in both text-emphasis-position and text-underline-position to effect language switches
<fantasai> C) Adopt inconsistent behavior: text-underline-position uses ‘auto‘ and text-emphasis-position uses UA stylesheet
Many CSSWG members like Emilio Cobos, TabAtkins, Miriam Suzanne, Rachel Andrew and fantasai casted their votes, resulting in the following resolution:
RESOLVED: add auto value for text-emphasis-position, and change the meaning of text-underline-position: auto to care about left vs right in vertical text
I definitely encourage you to read at the full minutes! Or if you don’t have the time, you can there’s a list just of resolutions.
CSSWG Minutes Telecon (2024-09-18) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #20
Having fun with Bramus’ new Caniuse CLI tool. This’ll save lots of trips to the Caniuse site!
Quick Hit #20 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #19
Two possible syntaxes for CSS masonry, one draft specification, and you get to share your opinions.
Quick Hit #19 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Clever Polypane Debugging Features I’m Loving
I’m working on a refresh of my personal website, what I’m calling the HD remaster. Well, I wouldn’t call it a “full” redesign. I’m just cleaning things up, and Polypane is coming in clutch. I wrote about how much I enjoy developing with Polypane on my personal blog back in March 2023. In there, I say that I discover new things every time I open the browser up and I’m here to say that is still happening as of August 2024.
Polypane, in case you’re unfamiliar with it, is a web browser specifically created to help developers in all sorts of different ways. The most obvious feature is the multiple panes displaying your project in various viewport sizes:
I’m not about to try to list every feature available in Polypane; I’ll leave that to friend and creator, Kilian Valkhof. Instead, I want to talk about a neat feature that I discovered recently.
Outline tabInside Polypane’s sidebar, you will find various tabs that provide different bits of information about your site. For example, if you are wondering how your social media previews will look for your latest blog post, Polypane has you covered in the Meta tab.
The tab I want to focus on though, is the Outline tab. On the surface, it seems rather straightforward, Polypane scans the page and provides you outlines for headings, landmarks, links, images, focus order, and even the full page accessibility tree.
Seeing your page this way helps you spot some pretty obvious mistakes, but Polypane doesn’t stop there. Checking the Show issues option will point out some of the not-so-obvious problems.
In the Landmarks view, there is an option to Show potentials as well, which displays elements that could potentially be page landmarks.
In these outline views, you also can show an overlay on the page and highlight where things are located.
Now, the reason I even stumbled upon these features within the Outline tab is due to a bug I was tracking down, one specifically related to focus order. So, I swapped over to the “Focus order” outline to inspect things further.
That’s when I noticed the option to see an overlay for the focus order.
This provides a literal map of the focus order of your page. I found this to be incredibly useful while troubleshooting the bug, as well as a great way to visualize how someone might navigate your website using a keyboard.
These types of seemingly small, but useful features are abundant throughout Polypane.
Amazing toolWhen I reached out to Kilian, mentioning my discovery, his response was “Everything’s there when you need it!”
I can vouch for that.
Clever Polypane Debugging Features I’m Loving originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Multiple Anchors
Only Chris, right? You’ll want to view this in a Chromium browser:
CodePen Embed FallbackThis is exactly the sort of thing I love, not for its practicality (cuz it ain’t), but for how it illustrates a concept. Generally, tutorials and demos try to follow the “rules” — whatever those may be — yet breaking them helps you understand how a certain thing works. This is one of those.
The concept is pretty straightforward: one target element can be attached to multiple anchors on the page.
<div class="anchor-1"></div> <div class="anchor-2"></div> <div class="target"></div>We’ve gotta register the anchors and attach the .target to them:
.anchor-1 { anchor-name: --anchor-1; } .anchor-2 { anchor-name: --anchor-2; } .target { }Wait, wait! I didn’t attach the .target to the anchors. That’s because we have two ways to do it. One is using the position-anchor property.
.target { position-anchor: --anchor-1; }That establishes a target-anchor relationship between the two elements. But it only accepts a single anchor value. Hmm. We need more than that. That’s what the anchor() function can do. Well, it doesn’t take multiple values, but we can declare it multiple times on different inset properties, each referencing a different anchor.
.target { top: anchor(--anchor-1, bottom); }The second piece of anchor()‘s function is the anchor edge we’re positioned to and it’s gotta be some sort of physical or logical inset — top, bottom, start, end, inside, outside, etc. — or percentage. We’re bascially saying, “Take that .target and slap it’s top edge against --anchor-1‘s bottom edge.
That also works for other inset properties:
.target { top: anchor(--anchor-1 bottom); left: anchor(--anchor-1 right); bottom: anchor(--anchor-2 top); right: anchor(--anchor-2 left); }Notice how both anchors are declared on different properties by way of anchor(). That’s rad. But we aren’t actually anchored yet because the .target is just like any other element that participates in the normal document flow. We have to yank it out with absolute positioning for the inset properties to take hold.
.target { position: absolute; top: anchor(--anchor-1 bottom); left: anchor(--anchor-1 right); bottom: anchor(--anchor-2 top); right: anchor(--anchor-2 left); }In his demo, Chris cleverly attaches the .target to two <textarea> elements. What makes it clever is that <textarea> allows you to click and drag it to change its dimensions. The two of them are absolutely positioned, one pinned to the viewport’s top-left edge and one pinned to the bottom-right.
If we attach the .target's top and left edges to --anchor-1‘s bottom and right edges, then attach the target's bottom and right edges to --anchor-2‘s top and left edges, we’re effectively anchored to the two <textarea> elements. This is what allows the .target element to stretch with the <textarea> elements when they are resized.
But there’s a small catch: a <textarea> is resized from its bottom-right corner. The second <textarea> is positioned in a way where the resizer isn’t directly attached to the .target. If we rotate(180deg), though, it’s all good.
CodePen Embed FallbackAgain, you’ll want to view that in a Chromium browser at the time I’m writing this. Here’s a clip instead if you prefer.
That’s just a background-color on the .target element. We can put a little character in there instead as a background-image like Chris did to polish this off.
CodePen Embed FallbackFun, right?! It still blows my mind this is all happening in CSS. It wasn’t many days ago that something like this would’ve been a job for JavaScript.
Multiple Anchors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Smashing Hour With Lynn Fisher
I’m a big Lynn Fisher fan. You probably are, too, if you’re reading this. Or maybe you’re reading her name for the first time, in which case you’re in for a treat.
That’s because I had a chance to sit down with Lynn for a whopping hour to do nothing more than gab, gab, and gab some more. I love these little Smashing Hours because they’re informal like that and I feel like I really get to know the person I’m talking with. And this was my very first time talking with Lynn, we had a ton to talk about — her CSS art, her annual site refreshes, where she finds inspiration for her work, when the web started to “click” for her… and so much more.
Don’t miss the bit where Lynn discusses her current site design (~24 min.) because it’s a masterclass in animation and creativity.
Smashing Hour With Lynn Fisher originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Two CSS Properties for Trimming Text Box Whitespace
The text-box-trim and text-box-edge properties in CSS enable developers to trim specifiable amounts of the whitespace that appear above the first formatted line of text and below the last formatted line of text in a text box, making the text box vertically larger than the content within.
This whitespace is called leading, and it appears above and below (so it’s two half-leadings, actually) all lines of text to make the text more readable. However, we only want it to appear in between lines of text, right? We don’t want it to appear along the over or under edges of our text boxes, because then it interferes with our margins, paddings, gaps, and other spacings.
As an example, if we implement a 50px margin but then the leading adds another 37px, we’d end up with a grand total of 87px of space. Then we’d need to adjust the margin to 13px in order to make the space 50px in practice.
As a design systems person, I try to maintain as much consistency as possible and use very little markup whenever possible, which enables me to use the adjacent-sibling combinator (+) to create blanket rules like this:
/* Whenever <element> is followed by <h1> */ <element> + h1 { margin-bottom: 13px; /* instead of margin-bottom: 50px; */ }This approach is still a headache since you still have to do the math (albeit less of it). But with the text-box-trim and text-box-edge properties, 50px as defined by CSS will mean 50px visually:
Disclaimer: text-box-trim and text-box-edge are only accessible via a feature flag in Chrome 128+ and Safari 16.4+, as well as Safari Technology Preview without a feature flag. See Caniuse for the latest browser support.
Start with text-box-trimtext-box-trim is the CSS property that basically activates text box trimming. It doesn’t really have a use beyond that, but it does provide us with the option to trim from just the start, just the end, both the start and end, or none:
text-box-trim: trim-start; text-box-trim: trim-end; text-box-trim: trim-both; text-box-trim: none;Note: In older web browsers, you might need to use the older start/end/both values in place of the newer trim-start/trim-end/trim-both values, respectively. In even older web browsers, you might need to use top/bottom/both. There’s no reference for this, unfortunately, so you’ll just have to see what works.
Now, where do you want to trim from?You’re probably wondering what I mean by that. Well, consider that a typographic letter has multiple peaks.
There’s the x-height, which marks the top of the letter “x” and other lowercase characters (not including ascenders or overshoots), the cap height, which marks the top of uppercase characters (again, not including ascenders or overshoots), and the alphabetic baseline, which marks the bottom of most letters (not including descenders or overshoots). Then of course there’s the ascender height and descender height too.
You can trim the whitespace between the x-height, cap height, or ascender height and the “over” edge of the text box (this is where overlines begin), and also the white space between the alphabetic baseline or descender height and the “under” edge (where underlines begin if text-underline-position is set to under).
Don’t trim anythingtext-box-edge: leading means to include all of the leading; simply don’t trim anything. This has the same effect as text-box-trim: none or forgoing text-box-trim and text-box-edge entirely. You could also restrict under-edge trimming with text-box-trim: trim-start or over edge trimming with text-box-trim: trim-end. Yep, there are quite a few ways to not even do this thing at all!
Newer web browsers have deviated from the CSSWG specification working drafts by removing the leading value and replacing it with auto, despite the “Do not ship (yet)” warning (*shrug*).
Naturally, text-box-edge accepts two values (an instruction regarding the over edge, then an instruction regarding the under edge). However, auto must be used solo.
text-box-edge: auto; /* Works */ text-box-edge: ex auto; /* Doesn't work */ text-box-edge: auto alphabetic; /* Doesn't work */I could explain all the scenarios in which auto would work, but none of them are useful. I think all we want from auto is to be able to set the over or under edge to auto and the other edge to something else, but this is the only thing that it doesn’t do. This is a problem, but we’ll dive into that shortly.
Trim above the ascenders and/or below the descendersThe text value will trim above the ascenders if used as the first value and below the descenders if used as the second value and is also the default value if you fail to declare the second value. (I think you’d want it to be auto, but it won’t be.)
text-box-edge: ex text; /* Valid */ text-box-edge: ex; /* Computed as `text-box-edge: ex text;` */ text-box-edge: text alphabetic; /* Valid */ text-box-edge: text text; /* Valid */ text-box-edge: text; /* Computed as `text-box-edge: text text;` */It’s worth noting that ascender and descender height metrics come from the fonts themselves (or not!), so text can be quite finicky. For example, with the Arial font, the ascender height includes diacritics and the descender height includes descenders, whereas with the Fraunces font, the descender height includes diacritics and I don’t know what the ascender height includes. For this reason, there’s talk about renaming text to from-font.
Trim above the cap height onlyTo trim above the cap height:
text-box-edge: cap; /* Computed as text-box-edge: cap text; */Remember, undeclared values default to text, not auto (as demonstrated above). Therefore, to opt out of trimming the under edge, you’d need to use trim-start instead of trim-both:
text-box-trim: trim-start; /* Not text-box-trim: trim-both; */ text-box-edge: cap; /* Not computed as text-box-edge: cap text; */ Trim above the cap height and below the alphabetic baselineTo trim above the cap height and below the alphabetic baseline:
text-box-trim: trim-both; text-box-edge: cap alphabetic;By the way, the “Cap height to baseline” option of Figma’s “Vertical trim” setting does exactly this. However, its Dev Mode produces CSS code with outdated property names (leading-trim and text-edge) and outdated values (top and bottom).
Trim above the x-height onlyTo trim above the x-height only:
text-box-trim: trim-start; text-box-edge: ex; Trim above the x-height and below the alphabetic baselineTo trim above the x-height and below the alphabetic baseline:
text-box-trim: trim-both; text-box-edge: ex alphabetic; Trim below the alphabetic baseline onlyTo trim below the alphabetic baseline only, the following won’t work (things were going so well for a moment, weren’t they?):
text-box-trim: trim-end; text-box-edge: alphabetic;This is because the first value is always the mandatory over-edge value whereas the second value is an optional under-edge value. This means that alphabetic isn’t a valid over-edge value, even though the inclusion of trim-end suggests that we won’t be providing one. Complaints about verbosity aside, the correct syntax would have you declare any over-edge value even though you’d effectively cancel it out with trim-end:
text-box-trim: trim-end; text-box-edge: [any over edge value] alphabetic; What about ideographic glyphs?It’s difficult to know how web browsers will trim ideographic glyphs until they do, but you can read all about it in the spec. In theory, you’d want to use the ideographic-ink value for trimming and the ideographic value for no trimming, both of which aren’t unsupported yet:
text-box-edge: ideographic; /* No trim */ text-box-edge: ideographic-ink; /* Trim */ text-box-edge: ideographic-ink ideographic; /* Top trim */ text-box-edge: ideographic ideographic-ink; /* Bottom trim */ text-box, the shorthand propertyIf you’re not keen on the verbosity of text box trimming, there’s a shorthand text-box property that makes it somewhat inconsequential. All the same rules apply.
/* Syntax */ text-box: [text-box-trim] [text-box-edge (over)] [text-box-edge (under)]? /* Example */ text-box: trim-both cap alphabetic; Final thoughtsAt first glance, text-box-trim and text-box-edge might not seem all that interesting, but they do make spacing elements a heck of a lot simpler.
Is the current proposal the best way to handle text box trimming though? Personally, I don’t think so. I think text-box-trim-start and text-box-trim-end would make a lot more sense, with text-box-trim being used as the shorthand property and text-box-edge not being used at all, but I’d settle for some simplification and/or consistent practices. What do you think?
There are some other concerns too. For example, should there be an option to include underlines, overlines, hanging punctuation marks, or diacritics? I’m going to say yes, especially if you’re using text-underline-position: under or a particularly thick text-decoration-thickness, as they can make the spacing between elements appear smaller.
Two CSS Properties for Trimming Text Box Whitespace originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
What’s Old is New
I collect a bunch of links in a bookmarks folder. These are things I fully intend to read, and I do — eventually. It’s a good thing bookmarks are digital, otherwise, I’d need a bigger coffee table to separate them from the ever-growing pile of magazines.
The benefit of accumulating links is that the virtual pile starts revealing recurring themes. Two seemingly unrelated posts published a couple months apart may congeal and become more of a dialogue around a common topic.
I spent time pouring through a pile of links I’d accumulated over the past few weeks and noticed a couple of trending topics. No, that’s not me you’re smelling — there’s an aroma of nostalgia in the air., namely a newfound focus on learning web fundamentals and some love for manual deployments.
Web Developers, AI, and Development FundamentalsUltimately, it is not about AI replacing developers, but about developers adapting and evolving with the tools. The ability to learn, understand, and apply the fundamentals is essential because tools will only take you so far without the proper foundation.
ShopTalk 629: The Great Divide, Global Design + Web Components, and Job TitlesChris and Dave sound off on The Great Divide in this episode and the rising value of shifting back towards fundamentals:
Dave: But I think what is maybe missing from that is there was a very big feeling of disenfranchisement from people who are good and awesome at CSS and JavaScript and HTML. But then were being… The market was shifting hard to these all-in JavaScript frameworks. And a lot of people were like, “I don’t… This is not what I signed up for.”
[…]
Dave: Yeah. I’m sure you can be like, “Eat shit. That’s how it is, kid.” But that’s also devaluing somebody’s skillset. And I think what the market is proving now is if you know JavaScript or know HTML, CSS, and regular JavaScript (non-framework JavaScript), you are once again more valuable because you understand how a line of CSS can replace 10,000 lines of JavaScript – or whatever it is.
Chris: Yeah. Maybe it’s coming back just a smidge–
Dave: A smidge.
Chris: –that kind of respecting the fundamental stuff because there’s been churn since then, since five years ago. Now it’s like these exclusively React developers we hired, how useful are they anymore? Were they a little too limited and fundamental people are knowing more? I don’t know. It’s hard to say that the job industry is back when it doesn’t quite feel that way to me.
Dave: Yeah, yeah. Yeah, who knows. I just think the value in knowing CSS and HTML, good HTML, are up more than they maybe were five years ago.
Just a Spec: HTML Finally Gets the Respect It DeservesJared and Ayush riffin’ on the first ever State of HTML survey, why we need it, and whether “State of…” surveys are representative of people who work with HTML.
[…] once you’ve learned about divs and H’s 1 through 6, what else is there to know? Quite a lot, as it turns out. Once again, we drafted Lea Verou to put her in-depth knowledge of the web platform to work and help us craft a survey that ended up reaching far beyond pure HTML to cover accessibility, web components, and much more.
[…]
You know, it’s perfectly fine to be an expert at HTML and CSS and know very little JavaScript. So, yeah, I think it’s important to note that as we talk about the survey, because the survey is a snapshot of just the people who know about the survey and answer the questions, right? It’s not necessarily representative of the broad swath of people around the world who have used HTML at all.
[…]
So yeah, a lot of interest in HTML. I’m talking about HTML. And yeah, in the conclusion, Lea Verou talks about we really do have this big need for more extensibility of HTML.
In a more recent episode:
I’m not surprised. I mean, when someone who’s only ever used React can see what HTML does, I think it’s usually a huge revelation to them.
[…]
It just blows their minds. And it’s kind of like you just don’t know what you’re missing out on up to a point. And there is a better world out there that a lot of folks just don’t know about.
[…]
I remember a while back seeing a post come through on social media somewhere, somebody’s saying, oh, I just tried working with HTML forms, just standard HTML forms the first time and getting it to submit stuff. And wait, it’s that easy?
Yeah, last year when I was mentoring a junior developer with the Railsworld conference website, she had come through Bootcamp and only ever done React, and I was showing her what a web component does, and she’s like, oh, man, this is so cool. Yeah, it’s the web platform.
Reckoning: Part 4 — The Way OutAlex Russell in the last installment of an epic four-part series well worth your time to fully grasp the timeline, impact, and costs of modern JavsaScript frameworks to today’s development practices:
Never, ever hire for JavaScript framework skills. Instead, interview and hire only for fundamentals like web standards, accessibility, modern CSS, semantic HTML, and Web Components. This is doubly important if your system uses a framework.
Semi-Annual Reminder to Learn and Hire for Web StandardsThis is a common cycle. Web developers tire of a particular technology — often considered the HTML killer when released — and come out of it calling for a focus on the native web platform. Then they decide to reinvent it yet again, but poorly.
There are many reasons companies won’t make deep HTML / CSS / ARIA / SVG knowledge core requirements. The simplest is the commoditization of the skills, partly because framework and library developers have looked down on the basics.
The anchor elementHeydon Pickering in a series dedicated to HTML elements, starting alphabetically with the good ol’ anchor <a>:
Sometimes, the <a> is referred to as a hyperlink, or simply a link. But it is not one of these and people who say it is one are technically wrong (the worst kind of wrong).
[…]
Web developers and content editors, the world over, make the mistake of not making text that describes a link actually go inside that link. This is collosally unfortunate, given it’s the main thing to get right when writing hypertext.
AI Myth: It lets me write code I can’t on my ownAt the risk of being old and out-of-touch: if you don’t know how to write some code, you probably shouldn’t use code that Chat GPT et al write for you.
[…]
It’s not bulletproof, but StackOverflow provides opportunities to learn and understand the code in a way that AI-generated code does not.
What Skills Should You Focus on as Junior Web Developer in 2024?Let’s not be old-man-shakes-fist-at-kids.gif about this, but learning the fundamentals of tech is demonstrateably useful. It’s true in basketball, it’s true for the piano, and it’s true in making websites. If you’re aiming at a long career in websites, the fundamentals are what powers it.
[…]
The point of the fundamentals is how long-lasting and transferrable the knowledge is. It will serve you well no matter what other technologies a job might have you using, or when the abstractions over them change, as they are want to do.
As long as we’re talking about learning the fundamentals…
The BasicsOh yeah, and of course there’s this little online course I released this summer for learning HTML and CSS fundamentals that I describe like this:
The Basics is more for your clients who do not know how to update the website they paid you to make. Or the friend who’s learning but still keeps bugging you with questions about the things they’re reading. Or your mom, who still has no idea what it is you do for a living. It’s for those whom the entry points are vanishing. It’s for those who could simply sign up for a Squarespace account but want to understand the code it spits out so they have more control to make a site that uniquely reflects them.
Not all this nostalgia is reserved only for HTML and CSS, but for deploying code, too. A few recent posts riff on what it might look like to ship code with “buildless” or near “buildless” workflows.
Raw-Dogging WebsitesIt is extraordinarily liberating. Yes, there are some ergonomic inefficiencies, but at the end of the day it comes out in the wash. You might have to copy-and-paste some HTML, but in my experience I’d spend that much time or more debugging a broken build or dependency hell.
Going BuildlessMax Böck in a follow-up to Brad:
So, can we all ditch our build tools soon?
Probably not. I’d say for production-grade development, we’re not quite there yet. Performance tradeoffs are a big part of it, but there are lots of other small problems that you’d likely run into pretty soon once you hit a certain level of complexity.
For smaller sites or side projects though, I can imagine going the buildless route – just to see how far I can take it.
Manual ’till it hurtsJeremy Keith in a follow-up to Max:
If you’re thinking that your next project couldn’t possibly be made without a build step, let me tell you about a phrase I first heard in the indie web community: “Manual ‘till it hurts”. It’s basically a two-step process:
- Start doing what you need to do by hand.
- When that becomes unworkable, introduce some kind of automation.
It’s remarkable how often you never reach step two.
I’m not saying premature optimisation is the root of all evil. I’m just saying it’s premature.
That’s it for this pile of links and good gosh my laptop feels lighter for it. Have you read other recent posts that tread similar ground? Share ’em in the comments.
What’s Old is New originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #18
PSA: Today’s the day that Google’s performance tools officially stops supporting the First Input Delay (FID) metric that was replaced by Interaction to Next Paint (INP).
Quick Hit #18 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.