Skip to main content Go to the homepage
State of the Browser

Anchors aweigh!
Tethering Elements in Pure CSS with Anchor Positioning.

We've all been there. You need a popover to attach to a button, but they aren't parent/child. You end up reaching for JavaScript, calculating coordinates, and wrestling with viewport edges. CSS Anchor Positioning is a recent API designed to solve exactly this. This talk is a practical, down-to-earth look at how it works. We'll explore the anchor() function, the position-anchor and position-area properties, and @position-fallback to build UIs that are truly context-aware and robust, all without the JS hacks.

Links
Transcript

Morning. Anchors aweigh. I always thought it was anchors away, like bombs away, because I'm a non-native English speaker. So I thought it was something like this, but then with an anchor. Planes don't drop anchors, it's boats. Oh, and boats go on water. Gemini, add water to the thing. Gemini, boats float on the water, raise up the water level. Yeah, like that. So anchors aweigh is like the act of the anchor getting lifted up. I never knew that. Mind blown. So my name is Bramus. I'm with Chrome DevRel. I'm working on CSS and UI-related features, scroll-driven animations, view transitions, all that fancy stuff. I really, really like it. I've been running a blog for 24 years now. If you don't have a blog, get your own blog. It's very valuable. I'm also into scuba diving and vinyl records. I'm also a member of the CSS working group, as Jake mentioned. So if you have any questions about that, come pull my sleeve. Also come say hi, because I'm kind of bad with faces and names. So just come pull my sleeve and say hi throughout the day. I will be here all day and also at the after party. I'm not here to talk about scuba diving or any of that today. I'm here to talk about my wonderful website. And if I scroll down, you can see all the content there. It's nice, right? But did you see it? Midway in the content, there is a little info icon there, like right there. So I want to go grab that thing and click it. So I come along with my pointer. I click it, and whoa, this extra piece of information gets shown. And Dave gets the hint. You should shout it now. Every time it's on screen, you should shout it. MORE ACID! Yeah. Dave's voice has been recorded, and he's on an acid track. You can ask him for the bandcamp link. But there's a bit of a problem with this, because the thing that I clicked is over there. And then the thing that gets shown, the little popover, is over there. And I don't want that. I want the popover to be close to the thing that I clicked. And I also want a little triangle. It's a nice visual thing. So you throw some JavaScript at it, right? Like, here, JavaScript. You get the thing that you clicked. You get the thing that you want to show. You do the getBoundingClient Rect, mathematics, whoo, and then you change the display style, and you get it. Also, the little triangle, CSS. CSS can do that. You inject a little square, but then you do some magic with the borders. So you get black, transparent, transparent, transparent. So the top border is black, but then it gets squished in by the other transparent borders. And then you get a triangle somehow. Magic. But it works. It works. You have this. Great. But then you scroll your page, and you're like, oh, crap. The thing should follow the other thing, right? So more JavaScript. You throw it on there. You're like, yay, it works. And then you scroll it out of view. You're like, oh, crap. It shouldn't be there. It should be down there. And the triangle should also be pointing that way. More JavaScript. And you're like, shouldn't this be simpler to do? Why do I need all this code? Yes, it should be simpler to do. You just npm install this wonderful package. Thank you for coming to my talk. [LAUGHTER] [APPLAUSE] So we have half an hour left. So maybe I can talk about view transitions, because I really like to talk about view transitions. Now, if you do want to see me talk about view transitions, come to All Day Hay. 7th of May, I will be there talking about view transitions and doing all sorts of crazy things with view transitions. So not today, but in May at All Day Hay. Buy a ticket. So back to the question. Shouldn't this be simpler to do? The more serious answer there is, yes, this should be simpler. And it can be simpler, because we have CSS anchor positioning. And if you look at it, it's like, this is in all browsers. Nice. But if you then open webstatus.dev, and you check it, it's like, baseline limited available, not baseline newly available. It's like, what's up with that? Well, it turns out there's still some differences between browsers. So yes, it's in all browsers, but it's not working consistently across all those browsers. Thankfully, though, there's Interop 2026. And you can see there, CSS anchor positioning is part of Interop 2026. The big asterisk with that one is that not all the WPT tests for anchor positioning made it into the Interop tests for anchor positioning. So a bit of a tricky situation. But still, we have green boxes everywhere in all browsers. So you can use it quite consistently in all those browsers. So CSS anchor positioning, it has a spec. And if you want to summarize a spec into one sentence, it's like, OK, this thing lets you position elements relative to other elements. And then it can also try some nice positions, fallbacks, to make sure there is no overflow happening. Here's some demos that are using this. So for example, if I hover one of the names here, you can see a little info box with some more info about that person. Here's another demo where I have a piece of text. And then you can see this little note that scrolls along with the highlighted word. You can see that the note is anchored to the piece of text. Another demo where you have four boxes. And if you hover one of the boxes, you can see this little highlight nicely moving from the one box to the other. It's getting anchored to the box that you are hovering. Kind of the same demo where you have this little arrow icon that follows the input that you have focused. Here's another one with carousels where you have this little dot navigation. It's not a child of the carousel. It's like an element somewhere in the DOM. But you can position it right on there. And then this is my favorite demo. You have a slider. This is by Temani Afif. Wonderful demo. It's also using scroll-driven animations to give you this little draggy swipey effect. I'm a huge fan of this demo, and also Temani's work in general. So let's take a look at the basics. How can we construct this type of thing where we position one element and stick it against another element so that it follows along? You start off with an anchor. Nothing too fancy there. Then you start off with a positioned element. And this one can be position fixed or position absolute. And then you need to connect the two together so that they work like that. So I've shown you the JavaScript approach, or a piece of the JavaScript approach. In CSS, it basically becomes this. You give the anchor, an anchor-name to identify it. This is a dashed indent. And then on the positioned element, you say your position anchor is that -- a, that name that you gave it. And then you link it up. And if you do that, your tooltip gets positioned on top of the anchor. And if you move your anchor around, it will nicely follow along with it. No joke. Just a CSS. If you move it to the top, it will be like, hold up. There's not enough room up there. I'm going to move it to the bottom for you. Nice. You get that for free with the browser. Then you move it back down. You move it back down. Like, OK, I think this is already pretty nice. So the core of the thing, which I've already mentioned, is that you give your anchor an anchor name. And then you use it as the value for the position anchor. And that way, you're linking the two together. Then the next declaration that you see here, this is position-area: block-start. And that tells the browser where it should try and position that positioned element, so our tooltip, relatively to that anchor. Now, to explain that to you, I need to explain the position area grid for you. And the position area grid is this wonderful thing. And it's pretty easy to construct, well, in most cases. So you take the anchor box, which is the box around the anchor. You take the containing block of your positioned element. And then you draw some lines, two vertical lines, two horizontal lines. And now you have a nine box grid around your anchor. So let's take a closer look at the grid. Well, we can see a top row there. We name that one top. You can see a center row. We name that one center. And the bottom row? Bottom, yay. And then we have columns as well. We have left, center, and right. If you want to use logical values, you can also do that. So you can do block start and block end, because that is the block axis. And then this is the inline axis. So you use inline start and inline end. You can also use physical things like x and y, because that depends on your screen. You can also target two columns if you want, span-left. Now, the thing that you need to know here is that it always spans from the center to the extra word. So here it spans from the center to the left. If you want to target the two columns there from this one and that one, well, then you use span-right, because it spans from the center to the right. You can also use span-inline-end, the logical right. And there's also span all to target all of those columns. And it also works with the rows, of course. So you also have a span all right over there. So that was our declaration there. Position-area, it says put the tooltip in the area which is identified on this nine box grid with the keyword block-start. So it tells to position that tooltip within the top row. That's basically what we have. Now, this green thing that I've highlighted here, it has a special word. Fancy things have special words. It is called the inset modified containing block, or IMCB for short. So we can change the IMCB, right? We can change where this tooltip should live. So we can say like, oh, your position-area should be top span-left. So we select the top row. And then from the center, we span to the left. Our IMCB becomes this. Then, of course, our tooltip also adjusts itself to be positioned within that IMCB. Here's another value which doesn't work. And it's because I'm mixing two of the keywords here. I'm mixing a logical variety with a non-logical variety. And that doesn't work. So here, block-start is a logical one. Span-left is not logical. That doesn't work. So I should be using block-start and then span-inline-start to make it work. So you can't mix and match the various keywords. But you can do combinations of it, as I've already shown you. So if you have this, there will be no IMCB. So your tooltip will just be positioned over there because it's not working. If you want to know more about these areas on the IMCB, my colleague, Una made a great tool for it. It's anchor-tool.com. And you can click one of the areas that you want the tooltip to be positioned in. And then it will also show you the IMCB, which is pretty nice. A variety on this is this tool by Temani Afif. And I really like it because it has more options. Like you want to see, I want to have two cells. I want to have four cells. I want to have nine cells. Span all, span all. It's a really nice, nice tool. So that's the IMCB. And what I really like about this IMCB is that within the IMCB, you can then move the positioned element around a bit. Because you can do things like justify self-start. And then the anchor will justify itself at the start position on that axis within the IMCB. You can also do align self-center. So it will center itself on the block axis within the IMCB. You can even stuff like an inset on that IMCB. And then the IMCB shrinks a little bit. I think this is really, really cool. To help you with that, I built another tool. It's like this. So the big green box here, that's a big anchor. It's like a massive anchor, really, really big. And I have a lot of positioned elements on there. So this one here is right span top. So it's positioned in the right column, and it's spanning from the center to the top. So if you hover any of these anchored elements right there, you will see your IMCB getting highlighted. If you click it, you then get some controls to modify the IMCB and also the alignment and the justification. So here I first set it to stretch, take up all the space. Then anchor-center, I really like that one, because it takes the center of the anchor. So the center of the anchor is right here, center span left. The element got positioned over there. Cool. So that's the IMCB. Let's talk about overflow management. Because we have this extra declaration here at the bottom to complete the whole two rules there, which allows you to do stuff like that. So if you move your anchor around, your IMCB gets-- well, your nine box area grid gets adjusted. And then your IMCB also gets adjusted. But there's not enough room there. The tooltip no longer fits within the IMCB. That's where 'position-try: flip-block' comes into play. It will say, hey, if you don't have enough room, try flipping on the block axis. So it will mirror the tooltip over the anchor itself on the block axis. So your tooltip will be positioned over there. You can add multiple values. You can do flip-block, flip-inline, and so on. It will try all those values one after the other until there is no overflow happening. This position-try flip-block thing, it's really clever, by the way. Because if you added something like a margin bottom on your original element, it will also flip that margin. So this margin-bottom, in this scenario, it will become margin-top, which is pretty, pretty sweet. Now, I can see some of you thinking at this point, but what about a little triangle? Because I've been showing you all these nice visualizations without the darn triangle. So yeah, we have this. We have this. We want to add a triangle, right? So we do it. We add-- we want to add a triangle right over there. So we add a little margin-bottom onto our positioned element. And then we use the injected thing with the triangle stuff with the border. And it works. So if we then move our tooltip around, ah, our position-try flip-block will kick into action. Oh. I want the triangle to also be on the other side. Thankfully, there's a solution for that. More CSS. More CSS. There's a solution for that. So the CSS for the triangle looks something like this, where we set the content to something. We position it at the top 100%. So it's underneath the positioned element, and then the border-color to form this sort of triangle. We can query which position-try-fallback is currently active on that anchored element. And for that, we have a new container query. Oh, overflow. Yeah, that better. So we can query which current fallback-- which fallback is currently active. So if the flip-block one is active, well, then we set some other border colors. So it will be transparent, transparent, black transparent. Yeah, that's the order. So our triangle, poing, comes like that. It goes pointing that way. Now, to make it work, we need to add a 'container-type: anchored', because we want to indicate to the browser, like, hey, you want to query this at a certain point in time, so you need to keep track of some stuff. And with that, I'm like, yes, this is nice. But-- but browser support looks like this for anchored container queries. So speak to your local browser vendor representative. Jake is over here. To get it in other browsers, because I think this is nice. So if you, at this point, are like, OK, nice visualizations, but does this actually work in a browser? Yes, this works in a browser, because this is a recording in Chrome. I click it. It moves around. So this actually works. And this is the code for that, which I think is nice. There's another way of doing it, by the way. Like in CSS, you have many approaches to get to the same result. There's another way to get to this thing, also using anchor positioning. So let's remove all of the stuff that we had there. So our tool tip gets positioned over there. We can use the almighty anchor function in CSS, which is also part of a CSS anchor positioning specification. And that one lets you read values from your anchored element relative to the containing block of the positioned element. So we can do something like 'anchor(top)'. And this resolves to a pixel value by which you measure the distance from the top of the containing block, the top part of the anchor towards the top of the containing block. So we get a pixel value. It says, like, this is 300 pixels away from the top. So we can use that one as a value for bottom on our tooltip. So we say, OK, the bottom of our tooltip should have that value. And boop, our tooltip magically moves to that side. So if we do something like 'left: anchor(center)', well, with anchor center, we measure the distance on the horizontal axis toward the center of the anchor. So we measure that distance. We draw a line over there. And then our tooltip gets positioned over there. Now, of course, it's a bit off. We want to move the tooltip centered above the anchor. So we add a little translate back to it. And that way, whoop, our tooltip gets positioned over there. I think this is pretty nice. It's more manual, though. Like, you had the automatic version with position-area. And this is like the manual version. So let's remove the translate, because I want to show you some more values. For example, if we do 'left: anchor(left)', we measure the left edge of the anchor. And we use that as the value for the left of our tooltip. So our tooltip gets positioned over there. We can also do 'left: anchor(right)'. So we take the right value of the anchor, so on that side. And then we use it as the value for the left of our tooltip. So our tooltip gets positioned over there. Or we can also do 'right: anchor(left)'. So we take the left edge again of our anchor and use that as the value for the right of the tooltip. So our tooltip gets positioned over there. Very manual, right? But this works. One note, though, is that you can only use this anchor function within other inset properties. So only within top right, bottom left. You can't use it on the margin, for example. And also, the axis needs to align. So on the left, as a value for the left, for example, you can't use the top value. Because you are setting something on the horizontal axis, but you are reading a value from the vertical axis. That also doesn't work. Some limitations. Now, I see some of you thinking, OK, this is pretty nice. But why? You had this automatic version. Why give us the manual version? Hold that thought. Because I can also see some of you thinking, what about a little triangle? Because you're bullshitting us again, because you didn't show a triangle. And yes, you can also do this with the manual version. So this is the non-manual version using position area. So we kick out position-area, and we do bottom anchor top. And we also remove our 9 box grid, because that no longer is present here. And we used to use position block, but then position-try flip-block. But position-try flip-block also doesn't work anymore here, because we are doing the manual thing. So we need something else to put there. Well, thankfully, we let you define custom try fallbacks to use. And for that, you have a new actual, which is @position-try. And you give it a dashed ident. And these are the values that you want to try. And then you can use that name again as the position try, so that the browser will try it. And if it's active, then it will do the thing. So in position try right there, you can use inset properties. You can use margin properties. But you can't do stuff like color. So it's only like position related properties that you can try and set. Again, browser support, Chrome 143 only. Talk to your local browser vendor representative about it. OK, so back to the thought, but why? Two reasons for that. One is animations. And the second one is multiple anchors. So if you look back at our overflow management that we had with the position-try flip-block, like in the non-manual version, it like boop, moved over to there. The thing is, you can't animate a position-area. So if the position-area here changes, thanks to the position-try, you can't animate that. It's a property-- is it animatable or not? I'm looking in that direction. Is it discreetly animatable? Discrete? OK. Is it discreet animable? So it will just flip over from one value to the other. You can't have a nice transition between that. You could throw some view transitions in the mix if you want to. Come see my talk at All Day Hey! But we don't want to use view transitions here. So we want to use something else. So this is about animating the anchor. It's not specifically about overflow management. So if you adjust our code again to what we had there with the manual version, if you take a close look-- it's the position over there. If you take a close look, that's just bottom and left that we are changing. And those properties are animatable. So we can add a transition to bottom and left. For convenience, because it wouldn't fit on the slide here, I'm using the all thing to transition. Please don't do that. Be very specific at what you transition. But for sake of brevity, I'm sticking with all. And this now will actually work. So if I move the anchor down, the tooltip will follow. But if the tooltip hits the bottom edge, it will nicely animate to its new position. Not that practical, kind of practical, but another demo where this is used is this one that I've shown you before. It's by Jhey Tompkins. And every time that you hover it, the highlight box moves around. So that highlight box is getting re-anchored to the thing that you are highlighting. And the code looks like this. So on hover, he's setting the anchor name only onto that hovered element. And he's using that as the position anchor value on some injected content. So the thing will nicely move, because he also has transitions right there. So that was the first but why animation. The second one is multiple anchors. Look at this thing right here. You have two boxes. Both of them are anchors. And then you can see the green box in between them. Well, the top left edge is anchored to the bottom right edge of the box. And then the bottom right corner of the green box is anchored to the top left corner of the blue box. You can do that, again, using the anchor function. But you can see here, we have added an extra identifier. So we are not reading the anchor right from just any anchor. We are being very specific which anchor we want to read the value from. A practical example of this is this demo right here by Roland Franke, where you have a threaded set of comments with these nice connecting lines going from the original comment to the threaded reply underneath there. So he's anchoring a box, which he gave a border-left and a border-bottom and some border-radius. He's anchoring it from this box straight to that box. This is pretty nice, but it is a static demo. Because if you make it dynamic and you can drag the boxes around, it's all working very fine until you cross the axis and then stuff breaks. The problem there is that if you read these values left and right, if you move the boxes like that, it nicely animates. So the left is, for example here, 500 pixels. And the right is 501 pixels. But once you over-drag, your left and right values are no longer in order, and you don't have a box visible. Thankfully, these anchor functions just resolve the pixel values. So you can add some mathematics to the game. You can select the minimum value of both. And just like that, our box pops up again. Problem still, though, is if you add a border-left and a border-bottom, like Roland did in his demo, the border is now at the wrong side. Solution for that, more boxes. Add more boxes. I designate four areas here, and I anchor each and everyone in the two anchors, but using the different values for top right, bottom, and left. So the only thing that you need to get from this slide is that I have a line box, a hot pink box, a yellow box, and a purple box. And so if I move the anchors around, you will see now it's a yellow box, now it's a purple one, now it's a hot pink one, and so on. So on each and every of those boxes between the two anchors, we can add borders, right? Right? Yeah? No. You can, but as you can see, the borders are always drawn, even though the element is only zero pixels high. So we don't like to have a conditional border. It doesn't really work, but you can fake it. This is another demo by Jhey Tompkins. And here you can see, OK, the lines are adjusting themselves, but these aren't real borders. He totally faked it using a mask on the thing, where you carve out and only that little piece of the background gets shown. I saw this as a challenge two weeks ago. I was like, I think I can fix this somehow. So here's a demo that is using real borders, and they work in all angles. So I'm using two positioned elements here, one for the vertical line and one for the horizontal line. You can change the border style, you can change the border width, the border color, and so on. The only downside is that this is the CSS for it. And that was shrunk down to fit it on the slide, and it's only one of the lines. So you also have one for the horizontal line. If you want to crank it up a notch, here's some crazy demo by Temani Afif, where he's anchoring stuff between the things, and then he's using like atan2() to figure out, OK, how should the lines draw under which angle? And then-- [LAUGHTER] OK. OK. I know. I know Jake. I know Jake. Oh. So yeah. That's it. Time for a lightning round under some slight pressure from Jake. Thanks, Jake. So a lightning round. Oh, come on, Jake. Stop it. Apology. All is forgiven. Lightning round, me under some pressure from Jake. CSS anchor-size(). In addition to the anchor function, you also have an anchor-size function. So with anchor(), you get the position, left, right, top, bottom. And with anchor-size(), you get the size of the anchor. So if you set the width of your positioned element to anchor size with your positioned element, it will take over the width from the anchor. Magic. Position-try-order. This is a nice one. So by default, your position-try-fallbacks will be evaluated from left to right. So it will go over them in order. And the first one that doesn't cause overflow, hey, you're the winner. But sometimes you want to say, no, no, I want to have the space with the most available width. And that way, you are changing the order. They are still getting checked, but then you're picking the one which has the most width. So if you measure the distances to the left and to the right, you see that the position there on the right, that one has the most width. So your positioned element will move on that side, even though it did fit in there on the left. CSS anchor-scope. This one is interesting. Because if you have multiple anchors with multiple positioned elements, they are using the same anchor name here. This is the same name. And the rule is, well, the last one that defined the name, that one wins. So here I have two positioned elements, but they are both anchored to anchor two. We don't want that. Well, you can limit the scope of your anchor name by using anchor-scope, my anchor, on a shared parent element. And just like that, that positioned element right there will find that anchor. And that positioned element right there will find that anchor. And it works. You can also use position-- 'anchor-scope: all' to contain all of the names. But I suggest you are very specific on which names you want to scope. In the CSS position-visibility, you have seen in this demo where the comment is anchored to the highlighted word. But you can see that the positioned element is only visible when the anchor is inside of the scrollport, so that when the anchor is visible. You have a bunch of keywords for that you can use. The initial value is anchors-visible. Show it as long as the anchor is visible. You also have always to always show it and play around with it. And implicit anchors, I really like that one as well. Because if you use something like the almighty popover, there is an implicit anchor connection created by the button that you clicked and the popover that you are showing. So this code here works without me setting up an anchor-name and then position-area, but not the position-anchor. So this is just working auto-magically. I like this. One caveat with this, though, is on your popover set 'inset: auto' or your position-try-fallbacks won't be used. This tripped me up four days ago and had a slight moment of panic in which my entire talk would no longer work. Inset auto is your thing there. And then transformed anchors. So if you have an anchor with a positioned element and then you transform your-- oh, this should be anchor. Oh, I have a mistake on my slides. We can fix that later on. So if you do a translate on your anchor, it will move to the side, but your positioned element will stay over there. And that is because the untransformed box of that anchor, well, it's still over there. We fixed that in Chrome 144. So now it follows transformed boxes. Nice. But as you can see, other browsers don't support it yet. Go talk to Jake and other people to get that in other browsers. Nice. And then the last thing of the quick-firing round or the lightning round is that the anchor must be fully laid out before the positioned element is, and this thing will trip you up. So here's an example. And do note that the tooltip, the positioned element, is a child of the '.anchor'. If you add something like this to your anchor, well, then suddenly that will create a containing block. So if you measured the containing block, which was originally the scroll port here because it's post-fixed, your containing block will shrink to that thing. And then your containing block and then the anchor box will be the same, so we can't really draw a grid in there. So all these declarations, they won't apply, and then your box will appear right over there at the bottom, which is not what you want. Go check out this article by Temani Afif. There's a lot more edge cases where things can trip you up. So the TLDR of anchor positioning is basically this. I've talked to you through all of this in half an hour, I think, or something like that. Before I close off, I can see some of you still here thinking about a little triangle in non-chromium browsers, because I told you that the fallback only works-- the anchored query fallback only works in chromium-based browsers. Well, Temani Afif, again, has solved that for us. So here he has a demo where the arrow nicely moves around. And he's using a shape with a clip path and a lot of mathematics to then move the thing around. He even cranked it up a notch, and he has these diagonal ones. I understand nothing of the math behind that. So with that, I'm at the end of my talk. Thanks. If you have any questions about anchor positioning, but also CSS or scuba diving, come talk to me, and I will see you later. Bye. Bye, Jake.

About Bramus Van Damme

Bramus Van Damme

Bramus is a web developer from Belgium. He’s part of the Chrome Developer Relations team at Google, focusing on CSS, Web UI, and DevTools. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since.

Before joining Google, Bramus worked as a freelance developer in various frontend and backend roles. For seven years he also was a College Lecturer Web & Mobile, educating undergrad students all about HTML, CSS, and JavaScript — in that order.