Skip to content

fix: insert position-area mapping styles into each target's own root - #446

Merged
jamesnw merged 9 commits into
oddbird:mainfrom
jpzwarte:fix/444-position-area-on-host
Aug 14, 2026
Merged

fix: insert position-area mapping styles into each target's own root#446
jamesnw merged 9 commits into
oddbird:mainfrom
jpzwarte:fix/444-position-area-on-host

Conversation

@jpzwarte

Copy link
Copy Markdown
Contributor

Fixes #444

position-area in a :host rule positions the shadow host, which lives in the outer tree, but the generated --pa-value-* mapping rule was inserted into the roots being polyfilled. A <style> in a shadow root never matches that root's own host, so the values stayed undefined and the host was left unpositioned.

Record the style container of each target while the rules are generated, and insert the stylesheet into those containers instead. Targets inside a polyfilled shadow root resolve to the same container as before.

`position-area` in a `:host` rule positions the shadow host, which lives
in the outer tree, but the generated `--pa-value-*` mapping rule was
inserted into the roots being polyfilled. A `<style>` in a shadow root
never matches that root's own host, so the values stayed undefined and
the host was left unpositioned.

Record the style container of each target while the rules are generated,
and insert the stylesheet into those containers instead. Targets inside a
polyfilled shadow root resolve to the same container as before.

Refs oddbird#444

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-polyfill ready!

Name Link
🔨 Latest commit 0ac7c96
🔍 Latest deploy log https://app.netlify.com/projects/anchor-polyfill/deploys/6a7e2f1b66c9050008c46ac8
😎 Deploy Preview https://deploy-preview-446--anchor-polyfill.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-position-wpt canceled.

Name Link
🔨 Latest commit 0ac7c96
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a7e2f1b6dadb100074c1c89

@jpzwarte
jpzwarte marked this pull request as ready for review July 25, 2026 10:24

@jamesnw jamesnw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a brief look so far. I would love a reproduction I can play with, as it isn't clear to me that this should work. I know anchor references can't cross shadow boundaries, but once an element has a default anchor, can it use it across shadow boundaries? Does that also work if you use anchor() without specifying an anchor?

Also, is this limited to position-area because we don't need to write custom styles for anchor-size() or anchor()?

Comment thread tests/e2e/shadow-dom.test.ts
@jpzwarte

jpzwarte commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Just a brief look so far. I would love a reproduction I can play with, as it isn't clear to me that this should work.

I'm adding an example for this specific case. Without the fix you get this:

CleanShot 2026-07-27 at 12 01 19@2x

With the fix:

CleanShot 2026-07-27 at 12 04 00@2x

@jamesnw jamesnw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this works. I've done a bit of thinking on whether this matches the spec.

In particular- https://drafts.csswg.org/css-anchor-position/#target:~:text=Note%3A%20An,them%2E%29

Note: An anchor-name defined by styles in one shadow tree won’t be seen by anchor functions in styles in a different shadow tree, preserving encapsulation. However, elements in different shadow trees can still anchor to each other, so long as both the anchor-name and anchor function come from styles in the same tree, such as by using ::part() to style an element inside a shadow. (Implicit anchor elements also aren’t intrinsically limited to a single tree, but the details of that will depend on the API assigning them.)

So :host{ bottom: anchor(--position-area-on-host top) } or :host{ position-anchor: --position-area-on-host } do not work. The example here works because we are setting a default anchor element with the style tag, which is in the same tree as where the anchor is defined.

If that matches your understanding, I think this makes sense, and there's just a few small changes. Thanks!

Comment thread src/transform.ts Outdated
Comment thread src/utils.ts Outdated
@jpzwarte

Copy link
Copy Markdown
Contributor Author

If that matches your understanding, I think this makes sense, and there's just a few small changes. Thanks!

It does match my understanding: :host { top: anchor(bottom) } works because the anchor is implicit; :host { top: anchor(--foo bottom) } doesn't because anchor-name: --foo is in a different tree.

@jamesnw jamesnw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

Comment thread src/utils.ts Outdated
Comment thread src/parse.ts Outdated
changed: false,
created: true,
css: '',
containers: new Set(),

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.

Thinking through it more, this approach of pushing an almost-StyleData object onto styleData here is sort of a smelly workaround in the first place. If we had parseCSS return a separate object with the new css and containers Set, I think that might be cleaner. I'll try it out and push a commit for review.

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 tried out this approach in 705df42

@jamesnw @jpzwarte What do you think?

Comment thread src/transform.ts
for (const container of containers) {
const styleEl = document.createElement('style');
styleEl.setAttribute(POLYFILLED_STYLE_ATTRIBUTE, 'true');
styleEl.textContent = css;

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 guess this now writes all the generated CSS to each container, when as far as I can tell we already know which CSS is needed by each container. Is there a good reason not to switch to a Map keyed by each container?

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.

Tried this approach out in a8b2fef

@jgerigmeyer
jgerigmeyer requested a review from jamesnw August 13, 2026 20:56

@jamesnw jamesnw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think these changes make sense!

@jamesnw
jamesnw merged commit 1daef9f into oddbird:main Aug 14, 2026
10 checks passed
@jpzwarte
jpzwarte deleted the fix/444-position-area-on-host branch August 15, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] position-area in a :host rule doesn't position the host

3 participants