A boats anchor agains a futuristic background with rectangle patterns

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.

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:

Two elements below each other in the center of the screen, one of them is a square in yellow with an anchor icon on it, the other has a 16/9 aspect ratio in green below it

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:

Two elements next each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is tethered to the yellow one on the right hand side withe it's stop starting at the center of the one with the anchor

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:

Two elements next each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is tethered to the yellow one on the right hand side withe it's stop starting at 20% height of the one with the anchor

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 inset-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 inset-area to our .anchored item:

.anchored {
  position-anchor: --anchortome;
  position: absolute;
  inset-area: top span-all;
}

This will give you the following result (explained below image)

Two elements above each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is sitting on top of the yellow one, center aligned

What happened here? The inset-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:

A yellow square centered on the screen with an anchor icon on it, it is centered in a 3 by 3 grid. The grid is drawn with green lines.

In our example inset-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 inset-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-options

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-options. 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-options: flip-inline, flip-block, flip-block flip-inline;
}

If the anchor now gets placed to the bottom right, this is the result:

A yellow square is at the bottom right of the screen with an anchor icon on it, There is a green rectangle attachted to it on the left hand side with the bottom of that rectangle at the center of the yellow square

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 inset-area example as well)

Creating our own position-try-options 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-options 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-options: --hold-left;
  overflow: auto;
  background: olive;
}

The value of the position-try-options 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
  • inset-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 default
  • most-width: Chooses the fallback that provides the most width for the anchored item
  • most-height: Chooses the fallback that provides the most height for the anchored item
  • most-inline-size: Chooses the fallback that provides the biggest inline-size for the anchored item
  • most-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.

A yellow square is at the center of the page with a small offset to the right. A green rectangle is hanging on the right edge of the yellow square, filling the remaining space of the screen on the right hand side. The green rectangle has some lorem ipsum placeholder text in it.

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-options: --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-options, 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-options. 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:

 in  css