Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 351 additions & 0 deletions content/blog/2026/anchor-overflow.md
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
Comment thread
jamesnw marked this conversation as resolved.
Outdated
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{
Comment thread
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));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't working in every situation yet.


&::part(editable-style){
Comment thread
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){
Comment thread
jamesnw marked this conversation as resolved.
Outdated
width: 100%;
margin-block-start: 3em;
}
Comment on lines +26 to +29
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Comment thread
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>
Comment thread
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Comment thread
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
Comment thread
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 }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly, Safari doesn't allow you to target ::part(label)::-webkit-slider-thumb, so I had to repeat this in every demo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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>

Comment thread
jamesnw marked this conversation as resolved.

### Rule #3 - anchor-center
Comment thread
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>
Comment thread
jamesnw marked this conversation as resolved.

Wait — what is `anchor-center`? It's a new value for the self alignment
Comment thread
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
Comment thread
jamesnw marked this conversation as resolved.
Outdated
The final case for `position-area` is that it specifies just 2 of the sections
Comment thread
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>
Comment thread
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`.
Comment thread
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.
Comment thread
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>
Comment thread
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
Comment thread
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>
Comment thread
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>
Comment thread
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>
Comment thread
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>
Comment thread
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this instruction of "Remove safe" assume I edited the justify-self value to be safe start?


<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>
Comment thread
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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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>
Comment thread
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 normal alignment should use safe or unsafe logic. Instead of just normal (the default) authors can now say normal safe or normal unsafe. Safe center-alignment (for example) will clamp at the start edge, since it's not possible to scroll back beyond the start of a container. Unsafe centering will stay centered even if that makes some of the overflow impossible to get to off the starting edge. I don't know the details of normal alignment, but if it comes with the possibility of unsafe (start-edge) overflow, this explicit toggle makes that adjustable.

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.
Binary file added src/images/blog/2026/anchor-overflow.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.