The CSS anchoring API is something that I’ve been following for at least a year now, and I’m happy to see that the first level is fully specced. Even though there are still some shortcomings that I’ll cover in this article as well, the available things look pretty solid and will help us a lot in creating some complex layout structures. Besides that, they are very much needed for the future of the HTML Popover API and stylable selects.
Before I get into the details of why some things are still missing in the Anchoring API, I want to get on the hype train. Because, no matter the small issues (or, let’s call them missed opportunities?) I spotted, this is something I wanted for a long time and it’s pretty awesome. The anchoring API has been inside of my future of UI talk for quite some time and in just a moments time, I had to change this from experimental code to specced code, and soon after as available in Chrome. Yes, you read that right, what we’ll be covering here is already available in Chrome 125 (at the moment of writing, the latest version).
For this article, we won’t be creating the most fancy examples out there, this is a (complete) basic introduction with clear examples. After reading this, you sould have a solid basic understanding on how this anchoring system works.
Note: This article and examples within have been updated on the 9th of August with the new spec. Some of those changes will only be available in the next two Chrome versions. As support for this feature is still limited to Chrome at the moment, I decided to update this article. You can find information about the updates here.
Anchoring API - two specs merged into one?
There are currently two major ways how you can get anchoring to happen. Without going too much into the history, this is the short version of why that is: There was an experimental implementation with the anchor()
function and @position-fallback
(not position-try, we’ll get there) when a new idea was launched by Webkit with an alternative solution using more of a grid-like system. From what I can tell, some frustrations happened and this certainly slowed things down, but this has some benefits:
- We get to have the best of both worlds, both ideas were great
- The original
anchor()
idea is probably more powerful and versatile, while the grid idea gives a more easy and quick everyday solution
Besides that, there are a few downsides:
- This is harder to teach to people as there are multiple ways in using anchors
- Both methods don’t have a fallback for arrow indications on tooltips and I wanted both methods to have it on first spec… (more on that later)
This is why, in this article, I’d like to focus on all the basics with simple demos, but I’ll be adding some links to other sources and tools as well. Let’s start off with taking a look at the basics of these two methods:
The basics of anchoring with CSS
As we’re going for some solid understanding, let’s get the tutorial started by creating 2 <div>
elements. For easy reference for the rest of this article, let’s name these with a class .anchor
and .anchored
.
<div class="anchor"></div>
<div class="anchored"></div>
Inside of the .anchor
I added an anchor icon for reference, this is purely optional. Let’s add some basic styling to differentiate the two:
body {
display: flex;
flex-direction: column;
place-content: center;
align-items: center;
margin: 0;
min-height: 100vh;
}
div {
height: 20vmin;
}
.anchor {
background: goldenrod;
aspect-ratio: 1;
}
.anchored {
background: olive;
aspect-ratio: 16/9;
}
The elements are placed in the center of our viewport by giving our body display: flex;
We gave both of the div
elements a relative height of 20vmin
(20% of the viewport’s smallest side). We also added a different aspect-ratio
to each of the items, this will come in handy visually later on.
Besides the extra background added to the <body>
for prettifying the demo a bit, this should pretty much be the result:
Method 1: using anchor()
The first method is the closest to the original proposal back when I first heard about this API. Let’s introduce a few new properties, anchor-name
and position-anchor
.
First, we’ll need to define our anchor, this is where anchor-name
comes in. Let’s add the following to our .anchor
.anchor {
anchor-name: --anchortome;
/* previous code */
}
The anchor name should be prefixed with --
, just as you would do with CSS variables but besides that, you can give it any name you want.
Next up, let’s add the position-anchor
to our .anchored
item, this will create the missing link (anchor) between the elements:
.anchored {
position-anchor: --anchortome;
/* previous code */
}
We don’t see much happening yet, that’s because we still need to position our items. To do this, we will absolutely position our .anchored
element, and we will introduce the anchor()
function to position it, this is the full updated code:
.anchored {
position-anchor: --anchortome;
position: absolute;
top: anchor(center);
left: anchor(right);
/* previous code */
}
What we’re saying here is:
Place the top of this div to the linked anchor’s center, and place the left of this div to the right of the linked anchor.
This results in the following:
But there are more options available, we could use a <percentage>
unit for our top, instead of using anchor(center)
as the value of our top
property, let’s add a percentage of anchor(20%)
. This would result in the following:
Besides percentages, you could also use logical properties for the alignment, for example:
.anchored {
position-anchor: --anchortome;
position: absolute;
inset-block-start: anchor(center);
inset-inline-start: anchor(end);
}
This way, you can anchor based on the writing mode.
Method 2: Using the position-area syntax
Let’s remove everything we did in the first method and use a different approach to how we can anchor something.
On our .anchor
, we’ll once again add the name, same idea as before:
.anchor {
anchor-name: --anchortome;
}
Next up we’ll link our anchor again and add a new property named position-area
to our .anchored
item:
.anchored {
position-anchor: --anchortome;
position: absolute;
position-area: top span-all;
}
This will give you the following result (explained below image)
What happened here? The position-area
method draws a 3x3 grid around our .anchor
behind the scenes, with the anchor item being in the middle column and row, something like this:
In our example position-area
is set to top
, making our item sit at the top row of our anchor grid. The second option is set to span-all
, telling the browser that it’s ok the span over the other two columns of the 3x3 grid.
You can span over both axes, part of an axis, or none at all, you can even use logical properties. I would get lost in getting to the foundations of this article so I’ll just refer to this handy anchor-tool created by Una Kravets, and suggest you play around with it to see the potential. To see all the spanning options out there, I’d like to refer to the anchoring spec as well
Note on the basic anchoring methods
Using the position-area
syntax will likely be the most commonly used. I’m talking about popovers, tooltips, fly-out menus, etc…
Although two methods make it harder to explain to people, I am happy that both of these methods came to exist simultaneously as the first method gives us much more flexibility for the times we need it. The power of these anchoring methods goes far beyond these basic demos, but they do show the capability,
Let’s take it one step further because when anchoring an item, we get some new things to play around with that give us the possibility to provide a fallback position to our anchored items based on the viewport.
Using the CSS anchoring basic fallback: position-try-fallbacks
More often than not, we want our anchoring to be visible no matter where the edge of our screen is. This happens a lot in software menus or tooltips for example. Some keywords were added for this to happen, a lovely addition. These keywords get referenced as “try-tactics”:
flip-block
flip-inline
flip-start
It’s great that this uses logical naming from the start… Let’s go back tot he first example, but this time around, we’ll give it some position-try-fallbacks
. We’ll make the first fallback a block flip, followed by an inline flip, if that still doesn’t work, it should flip both axes:
.anchored {
background: olive;
position-anchor: --anchortome;
position: absolute;
top: anchor(center);
left: anchor(right);
aspect-ratio: 16/9;
position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
}
If the anchor now gets placed to the bottom right, this is the result:
That’s flippin’ awesome!
Here is a full demo to play around with ( I added some controls):
(note: you could easily do this with the position-area
example as well)
Creating our own position-try-fallbacks with @position-try
What if we needed something more tailered to our needs and those basic try-tactics just aren’t enough? For those occasions we have the possibility to provide some position-try-fallbacks
ourselves. Let’s create a basic example with a custom fallback. We’ll use the same anchor as before but this time add some text inside the anchored element:
<div class="anchor">
<!-- icon -->
</div>
<div class="anchored">In this example, the anchor has a custom position-try</div>
What we want to achieve is to have our .anchored
item to be the exact height of our .anchor
, we can manage this by setting the top
and bottom
of the anchored item to 0
. The CSS of our anchor stays the same, but we need to update our .anchored
element, this is the fully updated code:
.anchored {
position: absolute;
padding: 20px;
position-anchor: --anchortome;
top: anchor(top);
bottom: anchor(bottom);
left: anchor(right);
align-self: stretch;
position-try-fallbacks: --hold-left;
overflow: auto;
background: olive;
}
The value of the position-try-fallbacks
property allows a custom name as a value. Same as with anchor-name
it should have two dashes (--
) to start. It can be combined with the basic try-tactics as well.
We’re going to try and position our .anchored
item completely left or right from our .anchor
filling in the full height. For this, we only need one fallback, but you can add as many as you want separated by a comma. Do note that the order matters.
Let’s create that custom try option with the @position-try
rule:
@position-try --hold-left {
top: anchor(top);
bottom: anchor(bottom);
right: anchor(left);
left: auto;
}
We just created our own little position-try-option, so cool that we can do this! Inside the @position-try
rule, only the following properties are allowed:
- inset properties
- margin properties
- sizing properties
- self-alignment properties
- position-anchor
- position-area
It offers a lot of possibilities, however, I still notice some issues with this from time to time, but then again, it’s still an early implementation. I have yet to play around with everything available here.
This is the result:
Picking what matters most for our anchoring options with position-try-order
Having the ability to pick different fallbacks is awesome, what’s even more handy is to have a bit of control over how the fallbacking should occur. With position-try-order
we can tell the browser what’s most important to us for the anchored item:
normal
: the defaultmost-width
: Chooses the fallback that provides the most width for the anchored itemmost-height
: Chooses the fallback that provides the most height for the anchored itemmost-inline-size
: Chooses the fallback that provides the biggest inline-size for the anchored itemmost-block-size
: Chooses the fallback that provides the biggest block-size for the anchored item
With more CSS features rising, it’s always good to have the logical variant of widths and heights baked in from the start. The same goes for position-try-order
. If you’re unfamiliar with logical properties, I recommend you get into those as soon as possible. There is a great episode of the CSS podcast about getting into logical property.
But why would we need this? As an example, I took the previous demo, and gave the anchor a left offset of 20vw
from the center.
Well, it still works… But it could be better:
We have more room on the left of the anchor and it could make sense to place the anchored item over there.
Since being anchored to the right is the default, it’s not doing that because it does have enough room to show it on the right of the anchor. So let’s update the anchored item and tell the browser to focus on getting our anchored item as wide as possible:
.anchored {
/* Same styles as before */
position-try-fallbacks: --hold-left;
position-try-order: most-width;
}
We can even write this a bit shorter with the shorthand. In that case, the order comes first and the options second:
.anchored {
position-try: most-width --hold-left;
}
And here is that final result, taking the most-width
into account:
I did run into issues…
With position-try-fallbacks
, I have run into the situation from time to time that the browser doesn’t understand my intent. This makes sense as there are still some bugs open related to this property. When creating an omnidirectional popover I just couldn’t get it perfect.
Future things to look out for in anchoring
There are a few things I’m looking forward to, some of them, I kinda wished would’ve shipped together with the things already covered in this article. But it is what it is…
anchor-scope
One property that is currently not yet available and has been specced is the ability to scope anchor names. When a design pattern is re-used, anchor-scope can prevent naming clashes across identical components.
When anchoring items, you need a unique name for now, and especially when creating tooltips, this could be a hassle. I played around with this before, setting an inline style to the buttons and popovers which set a variable with a unique name:
But this could be removed in the future and be turned into something like this in CSS (If I read the spec right)
If you’re reading this after anchor-scope
became available, I will leave the demo in here for historic value (how i had to do it and a remembering of how awesome it is to have the scope feature)
button {
anchor-name: --tooltip;
anchor-scope: --tooltip;
}
[popover] {
position-anchor: --tooltip;
}
Did you notice the previous CodePen with the arrow not showing in the correct position, well, that brings me to the following:
::tether pseudo-element? Or alternative idea
There has been talk about a ::tether
pseudo-element in the CSSWG, and other ideas were given as well. I’m not part of the CSSWG, but I’m really rooting for a solution for the arrows in combination with position-try-fallbacks
. It’s probably something clients will ask to have as well…
DevTools!
Yes! Just yes! More debugging tools for DevTools such as showing which try-option is currently active would be lovely.
A note on accessibility and feature flagging
Do note that even though you hang items to each other with the anchoring API, this is only visual. Although it’s now perfectly possible to tether elements to each other that are at complete different locations in the DOM, it has no effect on the accessibility tree, be mindful of that. Since this is an early feature, it’s something that we might just try out from time to time as a progressive enhancement. This is how you can check for support in your CSS:
@supports (anchor-name: --boat) {
/* Add your anchor awesomeness */
}
Looking forward to learning more at CSS Day!
In conclusion, I can only say that I’m absolutely happy that this Anchoring API has been specced. Apart from some issues, it’s clear that this will be a total game changer for popovers, flyout menus, awesome navigation, etc. Maybe Chrome released it a bit too quickly? 🤔 Just an honest thought, but I’m sure they are on top of the open bugs related to this API.
We’re just a few days away from CSS Day and I’m sure there will be a lot of talk about Anchoring Position. Tab Atkins will be giving a talk about it, really looking forward to that one.
We are getting spoiled with CSS, I was kind off expecting things to slow down a bit after the crazy two years we had, but that’s far from true. We’ve only started with Anchoring, scroll-driven animations, transition-behavior, and view transition and it seems we’ll soon be animating from height auto to a value as well. But that’s a story for another time.
Almost time for a few days of extreme CSS geeking at CSS Day!
Some more cool demos and resources about the anchoring API:
- Introducing the CSS anchor positioning API - by Una Kravets
- Future CSS: Anchor Positioning - by Roman Komarov
- CSS Anchoring positioning spec (really well written)