-
-
Notifications
You must be signed in to change notification settings - Fork 3
Anchor clamp demo article #1045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
d204c73
bc5c062
d14c76e
91253cc
aed2eca
d27a4c2
be5ea36
e8da19a
39c14d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,351 @@ | ||
| --- | ||
| title: Handling overflow with anchor positioning | ||
| sub: What is safer than safe? | ||
| date: 2026-01-20 | ||
| image: | ||
| src: blog/2026/anchor-overflow.jpg | ||
| alt: > | ||
| A small blue house | ||
| perched precariously, | ||
| overhanging the edge of a concrete base. | ||
| author: james | ||
| sponsors: true | ||
| tags: | ||
| - Article | ||
| - Anchor Positioning | ||
| - CSS | ||
| related_tag: Anchor Positioning | ||
| summary: | | ||
| When you use `position-area`, the most obvious impact is that you are setting an | ||
| area in which to place the anchored element. But what happens when the area | ||
| isn't the exact size as the element you're trying to place? | ||
| --- | ||
|
|
||
| <style> | ||
| /* inline-demo styles */ | ||
| inline-demo{ | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| border: medium solid black; | ||
| position: relative; | ||
| margin-block: var(--gutter); | ||
| --code-stripe-1: light-dark( | ||
| oklch(from var(--highlight) .99 .1 h), | ||
| oklch(from var(--highlight) .01 .1 h)); | ||
| --code-stripe-2: light-dark( | ||
| oklch(from var(--highlight) .97 .1 h), | ||
| oklch(from var(--highlight) .1 .1 h)); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't working in every situation yet. |
||
|
|
||
| &::part(editable-style){ | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| display: block; | ||
| white-space: pre; | ||
| font-family: monospace; | ||
| font-size: var(--code); | ||
| padding: var(--shim); | ||
| background: repeating-linear-gradient( | ||
| 45deg, | ||
| var(--code-stripe-1), | ||
| var(--code-stripe-1) 10px, | ||
| var(--code-stripe-2) 10px, | ||
| var(--code-stripe-2) 20px | ||
| ); | ||
| } | ||
| } | ||
| </style> | ||
| <style> | ||
| /* Styles for demos in this article */ | ||
| inline-demo{ | ||
| &::part(slider){ | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| width: 100%; | ||
| margin-block-start: 3em; | ||
| } | ||
|
Comment on lines
+26
to
+29
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the first three demos (and maybe for some others) I think it was easier to see what happens when I added a bottom margin, but I don't know if that makes sense everywhere (yet at least) |
||
|
|
||
| &::part(label){ | ||
| position: absolute; | ||
|
|
||
| background: var(--accent); | ||
| color: var(--bg); | ||
| padding: var(--half-shim); | ||
| } | ||
| } | ||
| </style> | ||
|
|
||
| {% callout 'note', false %} | ||
|
|
||
| **A note on the demos** | ||
|
|
||
| These demos use a range input as an easy movable element. While movement isn't | ||
| required for anchor positioning, it's useful for demos to show how it works in a | ||
| variety of situations. Unfortunately, Firefox doesn't support yet using the | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| thumb as an anchor, and there is an [open | ||
| bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1993699). | ||
|
|
||
| Also, the CSS shown with each demo is editable, so play around to see what's | ||
| going on! | ||
|
|
||
| {% endcallout %} | ||
|
|
||
| Anchor positioning handles this by applying self alignment values to position | ||
| the element close to the anchor. You likely are familiar with these values from | ||
| using them in grid layouts. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| #root{ | ||
| block-size: 5em; | ||
| display: grid; | ||
| } | ||
| </style> | ||
| <style part="editable-style" contenteditable>[part="label"] { | ||
| align-self: start; | ||
| justify-self: end; | ||
| }</style></code> | ||
| <div id="root"> | ||
| <div part="label">Move me by changing the *-self values</div> | ||
| </div> | ||
| </template> | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| </inline-demo> | ||
|
|
||
| There are many possible values for `position-area`, but they are essentially | ||
| different ways of specifying which areas you want to pick within a 3x3 grid. On | ||
| each axis, you have a start, center, and end area. You can pick any combination | ||
| except for start and end without the center, as it has to be contiguous. | ||
|
Comment on lines
+80
to
+81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this last sentence hard to follow. (It also took me a bit to realize that this paragraph is about something not shown in the demo directly before it) |
||
|
|
||
| For an in-depth look at `position-area`, my [article on | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| `position-area`](/2025/02/25/anchor-position-area/). | ||
|
|
||
| ## Alignment rules | ||
|
|
||
| There are just 3 rules that determine what alignment is used, based on the value | ||
| of the `position-area` that you specified. | ||
|
|
||
| ### Rule #1 - center | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
|
|
||
| For each axis, if `position-area` only specifies the `center` track, then the | ||
| alignment is `center`. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb {anchor-name: --thumb;} | ||
| [part="label"]{ position-anchor: --thumb; pointer-events: none; opacity: .8 } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Annoyingly, Safari doesn't allow you to target
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| position-area: center center; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">place-self: center</div> | ||
| </template> | ||
| </inline-demo> | ||
|
|
||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| ### Rule #3 - anchor-center | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| If `position-area` specifies all three sections in the axis, then the alignment | ||
| is `anchor-center`. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; } | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| position-area: block-start; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">justify-self: anchor-center</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| Wait — what is `anchor-center`? It's a new value for the self alignment | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| properties that aligns to the center of the anchor element. This differs from | ||
| `center`, which aligns to the center of the new containing block created by | ||
| `position-area`. | ||
|
|
||
| ### Rule #3 - everything else | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| The final case for `position-area` is that it specifies just 2 of the sections | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| on the axis. In other words, it selects one edge, and perhaps the center, but | ||
| not the other edge. In this situation, the alignment is whatever puts it closest | ||
| to the anchor. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; } | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| position-area: start; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">place-self: end</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| {% callout 'note', false %} | ||
|
|
||
| Of course, the spec isn't just "whatever puts it closest". The spec says that | ||
| the alignment is "toward the non-specified side track". So if you specify | ||
| "start" or "start and center", then the alignment will be `end`, and if you | ||
| specify "end" or "end and center", then the alignment will be `start`. | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
|
|
||
| While I find this to be technically useful, it doesn't really help my mental | ||
| model of how this works, so I just think of it as "whatever is closest". | ||
|
|
||
| {% endcallout %} | ||
|
|
||
| ## Overriding the defaults | ||
|
|
||
| But these are just the default values, and you can choose to override them. I | ||
| haven't seen compelling use cases for this, but I'm guessing someone will come | ||
| up with a use case. I'm guessing `stretch` will be the most useful override. | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; position-area: block-start; } | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| align-self: stretch; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">Fascinating info but longer</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| ## Overflowing the containing block | ||
|
|
||
| A common use case for anchor positioning is adding a popover to a word in text. | ||
| In these situations, you don't have a way to know where on the screen the word | ||
| will appear, or by extension where teh anchored element will appear. While you | ||
|
jamesnw marked this conversation as resolved.
Outdated
|
||
| could use `position-try-options` to specify what happens when the anchored | ||
| element overflows, there's a good chance you won't have to. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| span { anchor-name: --span; outline: var(--accent) medium solid; } | ||
| div{ position: absolute; position-anchor: --span; position-area: bottom; } | ||
| </style> | ||
| <p contenteditable> | ||
| Feel free to edit this text, which has a single <span>span</span> element | ||
| that is the anchor element. I think it's pretty neat that the anchor | ||
| positioning still works while you type! | ||
| </p> | ||
| <div part="label">Positioned element</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| You may have noticed that the label stays within its containing block, even if | ||
| that means that it is no longer positioned right at the anchor's center. This is | ||
| the default behavior for absolutely positioned elements. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; position-area: block-start } | ||
| </style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">CSS is awesome</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| In some cases, there isn't a compelling reason that the label can't overflow its | ||
| containing block. On this page, the label is small, and the containing block has | ||
| healthy margins (except on small screens). In this case, we could specify that | ||
| we want `unsafe` alignment. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; position-area: block-start } | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| justify-self: unsafe anchor-center; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">CSS is awesome</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| You may think that `safe` is the opposite of `unsafe`, but that's not quite the | ||
| case. `safe` alignment is only safe on the start-side, meaning this demo | ||
| overflows on the right side, but doesn't on the left side. | ||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; position-area: block-start } | ||
| </style> | ||
| <style> | ||
| ::-webkit-slider-thumb {anchor-name: --thumb;} | ||
| [part="label"]{position-anchor: --thumb;} | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| justify-self: safe anchor-center; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">CSS is awesome</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| So: | ||
| - unspecified stops overflow on both sides | ||
| - `safe` stops overflow only on the start side | ||
| - `unsafe` allows overflow on both sides | ||
|
|
||
| I initially found this confusing, as specifying `safe` may make it less safe | ||
| depending on the situation. In this example, the area specified by `position-area` | ||
| includes the `block-end`, so the implicit `justify-self` value is `start`. By | ||
| specifying `safe start` instead, the label overflows on the end side. Remove | ||
| `safe` to see the difference. | ||
|
Comment on lines
+311
to
+312
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this instruction of "Remove |
||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| ::-webkit-slider-thumb { anchor-name: --thumb; } | ||
| [part="label"]{ position-anchor: --thumb; } | ||
| </style> | ||
| <style part="editable-style" contenteditable>div { | ||
| position-area: block-start inline-end; | ||
| justify-self: safe start; | ||
| inline-size: max-content; | ||
| }</style> | ||
| <input type="range" part="slider"></input> | ||
| <div part="label">CSS is awesome and is totally a language</div> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| In the context of anchor positioned elements, where overflow can easily happen | ||
| on either side, the `safe` keyword doesn't quite make sense. However, when we | ||
| think of it for normal text, this is the behavior that we often expect. For | ||
| instance, in the "CSS is awesome" meme, we want text to overflow on the end, but | ||
| not at the start. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm. The CSS is awesome meme doesn't have a chance of overflowing the start side, because it's aligned to that side. To understand 'safe' I think it's best to use a centered or end-aligned example in a scrolling container. Overflowing one side is safe because you can scroll that way. But there's no negative scrolling, so it's less safe to overflow the start. I think that would also be more clear in the examples above if they used a scroll container – so they don't just have visible overflow. Safety doesn't matter as much with visible overflow, and so the terms can seem confusing. But it matters a lot in a scroll container. |
||
|
|
||
| <inline-demo> | ||
| <template shadowrootmode="open"> | ||
| <style> | ||
| :host{container-type: inline-size; inline-size: 50cqi; place-self: center;} | ||
| p{ font-size: 40cqi; margin: 2cqi; line-height: .8em} | ||
| </style> | ||
| <p>CSS is awesome</p> | ||
| </template> | ||
| </inline-demo> | ||
|
jamesnw marked this conversation as resolved.
|
||
|
|
||
| ## Future improvements | ||
|
|
||
| While `safe` and `unsafe` behavior has been part of grid and flex layouts for | ||
| quite some time, I'm guessing many authors (including me) will encounter it for | ||
| the first time (or in new situations) while using anchor positioning. I expect | ||
| with more use cases, CSS will make improvements to the capabilities of overflow | ||
| alignment. | ||
|
|
||
| Currently, you can't specify an overflow alignment keyword without also | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think 'safe' and 'unsafe' are introduced as 'overflow alignment keywords' anywhere, so I wasn't sure if that's what this refers to? |
||
| specifying the alignment, but there's a [CSSWG | ||
| issue](https://github.com/w3c/csswg-drafts/issues/12920) to allow that. I also | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mirisuzanne Is this actually what the issue is saying? I thought it was, but then re-reading makes me think it might not be?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this issue is just about giving authors the ability to specify if the |
||
| think it would be useful to add a keyword for the default behavior, so it could | ||
| be set like `safe` and `unsafe`, and potentially add a keyword like `safe-end` | ||
| that would overflow on the start, but not the end. | ||
Uh oh!
There was an error while loading. Please reload this page.