Skip to content

Add tooltip custom element with position-anchor support on the :host#409

Open
jpzwarte wants to merge 4 commits into
oddbird:mainfrom
jpzwarte:fix/408-position-anchor-on-host-custom-element
Open

Add tooltip custom element with position-anchor support on the :host#409
jpzwarte wants to merge 4 commits into
oddbird:mainfrom
jpzwarte:fix/408-position-anchor-on-host-custom-element

Conversation

@jpzwarte

@jpzwarte jpzwarte commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #408

What this fixes

When position-anchor is set on a custom element host (i.e. via a :host rule in an adopted stylesheet), the polyfill failed to position the element. Three separate issues combined to cause this:

  1. :host targets were never matched. querySelectorAll(':host') returns nothing when called from within a shadow root, so the polyfill never recognised the custom element as a target. Fixed in src/dom.ts by resolving :host/:host(...) selectors directly against the shadow root's host element.

  2. The anchor lived outside the polyfilled roots. The anchor element (e.g. a <div> in the light DOM) was not in any of the shadow roots passed to polyfill(), so getAnchorEl couldn't find it. Fixed in src/parse.ts by also searching the target element's own root node when looking up its anchor.

  3. Inline styles set via typed CSSOM were silently dropped. Setting el.style.anchorName = ... in a browser without native anchor-positioning support discards the unknown declaration, leaving no style attribute for the polyfill to read. Fixed in the demo (shadow-dom.html) by writing the declarations as raw attribute strings via setAttribute('style', ...), which browsers preserve literally.

New demo

A <position-anchor-on-host> custom element is added to shadow-dom.html. Its connectedCallback wires up the anchor link at runtime:

<div id="anchor" class="anchor">Anchor</div>
<position-anchor-on-host for="anchor">Target</position-anchor-on-host>
<script>
class PositionAnchorOnHost extends HTMLElement {
  connectedCallback() {
    const targetId = this.getAttribute("for"),
      target = targetId && this.getRootNode().getElementById(targetId);

    this.setAttribute("style", "position-anchor: --position-anchor-on-host");
    target?.setAttribute("style", "anchor-name: --position-anchor-on-host");

    this.attachShadow({ mode: "open" });

    const sheet = new CSSStyleSheet();
    sheet.replaceSync(`
      :host {
        top: anchor(top);
        left: anchor(center);
        position: absolute;
        translate: -50% -100%;
      }
    `);
    this.shadowRoot.adoptedStyleSheets = [sheet];
    this.shadowRoot.innerHTML = "<slot></slot>";
  }
}
customElements.define("position-anchor-on-host", PositionAnchorOnHost);
</script>

@netlify

netlify Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-position-wpt canceled.

Name Link
🔨 Latest commit 7bcaa38
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a2c734703ed1300089aef0b

@netlify

netlify Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-polyfill ready!

Name Link
🔨 Latest commit 7bcaa38
🔍 Latest deploy log https://app.netlify.com/projects/anchor-polyfill/deploys/6a2c7347c925eb00081eee74
😎 Deploy Preview https://deploy-preview-409--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.

@jpzwarte jpzwarte marked this pull request as ready for review June 12, 2026 07:49
@jgerigmeyer jgerigmeyer requested a review from jamesnw June 12, 2026 17:18

@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 is adding a bug. If the position-anchor is defined in the Constructed Stylesheet, on :host, and the anchor-name is defined in the light dom, browsers do not find the anchor-name. With this change, the polyfill does find the anchor.

:host{
  position-anchor: --a;
}

I'm not entirely certain of what is correct per the spec here. My understanding of the spec is that the target should not be able to find the anchor. The anchor has to be named in the same tree where it is used.

If that's the case, I'd like to figure out the more general pattern we could apply, so that the polyfill works for your example, without overeagerly exposing anchor names.

Comment thread shadow-dom.html
Comment on lines +75 to +82
this.setAttribute(
'style',
'position-anchor: --position-anchor-on-host',
);
target?.setAttribute(
'style',
'anchor-name: --position-anchor-on-host',
);

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.

Is this necessary for the functionality? I think you can test the same functionality by setting the style directly on the light dom elements, and it makes it a bit easier to understand.

Comment thread shadow-dom.html
Comment on lines +365 to +370
--element-color: var(--target, var(--outer-anchored));
background: var(--element-color);
border: thin solid var(--border);
border-radius: var(--radius-1);
color: white;
font-weight: bold;

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.

Suggested change
--element-color: var(--target, var(--outer-anchored));
background: var(--element-color);
border: thin solid var(--border);
border-radius: var(--radius-1);
color: white;
font-weight: bold;

I think we can exclude the styling CSS from the code block to simplify the example (but keep it in the actual custom element definition).

Comment thread src/dom.ts
if (trimmed === ':host') {
return true;
}
const match = /^:host\(\s*(.+?)\s*\)$/.exec(trimmed);

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, but could we parse it as CSS rather than using regex? I think that might allow us to handle selector lists like :host(.blue, .red)

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-anchor pointing from the :host of a custom element to another element is broken

2 participants