Skip to content

fix: add patchCSSOMto support setting anchor positioning properties in JS - #447

Open
jpzwarte wants to merge 15 commits into
oddbird:mainfrom
jpzwarte:fix/445-cssom-not-working
Open

fix: add patchCSSOMto support setting anchor positioning properties in JS#447
jpzwarte wants to merge 15 commits into
oddbird:mainfrom
jpzwarte:fix/445-cssom-not-working

Conversation

@jpzwarte

@jpzwarte jpzwarte commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #445

patchCSSOM() (opt-in, exported from /fn) makes every anchor positioning property the polyfill supports settable from JavaScript: anchor-name, anchor-scope, position-anchor, position-area, and the position-try properties. position-visibility is left out, as the polyfill doesn't support it. It defines each on CSSStyleDeclaration and stores the value in the custom property the polyfill shifts that declaration into internally — the browser can't drop a custom property, and it's already what the polyfill reads back. setProperty(), getPropertyValue() and removeProperty() take the dashed names as well.

cascadeCSS then restores those declarations to the property they were set on, before anything parses them, so a value set through the CSSOM is indistinguishable from one written in a stylesheet. That's what makes the properties beyond anchor-name/position-anchor work at all, and it keeps the CSSOM out of every parser — no special cases in parse.ts, position-area.ts or fallback.ts.

'anchorName' in element.style becomes true once it has run. CSS.supports('anchor-name: --a') is unaffected, and is what the README now recommends for feature detection.

Two bugs fixed along the way, both of which predate the CSSOM work:

  • Inline styles inside a shadow root were never collected: fetchInlineStyles() only searched document. It now searches the polyfill roots too — both, since a shadow-scoped run still needs its host and any light-DOM anchors.
  • transformCSS() corrupted an element's style attribute whenever the polyfill generated a fallback rule for that element's inline styles. It pulled the declarations back out with a fixed-length slice, which breaks as soon as anything else shares the block, writing a mangled selector into the attribute and losing the element's real styles. Generated rules now go into a stylesheet of their own. This was already reachable from plain HTML (<div style="position-try-fallbacks: --flip">), where the fallback silently never applied either.

Adds demos to index.html and shadow-dom.html.

jpzwarte and others added 2 commits July 25, 2026 12:17
`anchor-name` and `position-anchor` assigned from JavaScript are dropped
by the CSSOM in a browser without native support, so nothing lands in the
`style` attribute the polyfill reads. The demo wires up its anchor at
runtime the way a design system component would, and does not work as a
result.

Refs oddbird#445

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 e1bdda2
🔍 Latest deploy log https://app.netlify.com/projects/anchor-polyfill/deploys/6a83264fb310930008f687a0
😎 Deploy Preview https://deploy-preview-447--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 e1bdda2
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a83264f32aa9a0008a806e8

@jpzwarte
jpzwarte marked this pull request as ready for review July 25, 2026 11:48

@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.

Mostly conceptual questions at this point-
Idea- instead of writing the unsupported values to the style tag, could we shift the values to custom properties? I think that would sidestep a lot of this. We could do it like the shifted properties in cascade.ts, so like --anchor-name-${INSTANCE_UUID}, and then it might just work when we need to read it.

Does this change make it work better for Lit, for instance?

This is useful for non-shadow DOM things, correct? It might be worth not tying the examples together.

Would it be possible to expose this as an option when the polyfill is run, or does it need to be applied as a separate step, so that the application can do some work before the polyfill can be run?

Comment thread src/cssom.ts Outdated
Comment thread src/cssom.ts Outdated
// patch it never sees anchors that are wired up from JavaScript.
const PATCHED_PROPERTIES = {
anchorName: 'anchor-name',
positionAnchor: 'position-anchor',

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.

Why not positionArea or other anchor-related properties?

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.

Because anchor-name and position-anchor are the dynamic parts that i'm setting from Lit. Example:

<sl-button id="button">Button</sl-button>
<sl-tooltip for="button">Tooltip</sl-tooltip>

Which results in:

<sl-button id="button" style="anchor-name: --sl-tooltip-1">Button</sl-button>
<sl-tooltip for="button" style="position-anchor: --sl-tooltip-1">Tooltip</sl-tooltip>

Everything else is part of :host.

I could add more properties of course, but i'm not sure its worth it?

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.

Perhaps including at least position-area as well is a good idea. I can at least think of a web component where you could specify where it is anchored. But where do you stop?

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.

There are only a handful of new properties for anchor positioning, and I'd rather just support them all.

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.

See 5b315b0. This was more than just adding more properties to PATCHED_PROPERTIES:

patchCSSOM works by stashing whatever you assign into a private custom property, so el.style.positionArea = 'top' really lands in the style attribute as --position-area-<id>: top. Nothing else in the polyfill knows to look there — the two properties that already worked did so because two spots in the parser had been hand-taught to recognise that private spelling.

So rather than hand-teach five more spots (which would have made every author rule match twice, since the cascade leaves both spellings behind), this commit adds one step to cascadeCSS that renames the private property back to the real one before anything parses it; from there the CSSOM-set value is indistinguishable from CSS the author wrote, and every existing parser handles it for free.

The position-try properties also had to be added to the shifted list so their values survive being written back to the style attribute, and that in turn exposed a bug where transformCSS mangled that attribute whenever the polyfill generated a fallback rule for an inline style.

Comment thread src/cssom.ts Outdated
@jpzwarte

Copy link
Copy Markdown
Contributor Author

Would it be possible to expose this as an option when the polyfill is run, or does it need to be applied as a separate step, so that the application can do some work before the polyfill can be run?

I think it's a matter of preference. If you run polyfill() after HTML has rendered, then it's too late. So in that case you would need to call patchCSSOM() before it. Otherwise it could be an option.

@jpzwarte

jpzwarte commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

This is useful for non-shadow DOM things, correct? It might be worth not tying the examples together.

True, but where would we put an example for it? A new cssom.html page?

@jpzwarte

Copy link
Copy Markdown
Contributor Author

Idea- instead of writing the unsupported values to the style tag, could we shift the values to custom properties? I think that would sidestep a lot of this. We could do it like the shifted properties in cascade.ts, so like --anchor-name-${INSTANCE_UUID}, and then it might just work when we need to read it.

Done!

@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.

This is looking pretty close, I think!

This is useful for non-shadow DOM things, correct? It might be worth not tying the examples together.

True, but where would we put an example for it? A new cssom.html page?

It can just be added to index.html. I know it's super long, and eventually it would be nice to have a nicer way to add demos.

Comment thread src/cssom.ts Outdated
// patch it never sees anchors that are wired up from JavaScript.
const PATCHED_PROPERTIES = {
anchorName: 'anchor-name',
positionAnchor: 'position-anchor',

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.

There are only a handful of new properties for anchor positioning, and I'd rather just support them all.

Comment thread src/cssom.ts Outdated
Comment thread src/parse.ts Outdated
Comment thread src/parse.ts Outdated
@jpzwarte jpzwarte changed the title fix: add patchCSSOMto support setting anchor-name and position-anchor in JS fix: add patchCSSOMto support setting anchor positioning properties in JS Aug 15, 2026
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] anchor-name and position-anchor set via CSSOM are invisible to the polyfill

2 participants