Skip to content

oddbird/css-anchor-positioning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,258 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

CSS Anchor Positioning Polyfill

Build Status npm version Netlify Status

The CSS anchor positioning specification defines anchor positioning, "where a positioned element can size and position itself relative to one or more 'anchor elements' elsewhere on the page." This CSS Anchor Positioning Polyfill supports and is based on this specification.

Browser Support

  • Firefox 54+ (includes Android)
  • Chrome 51 - 124 (includes Android)
  • Edge 79 - 124
  • Safari 10+ (includes iOS)

Anchor positioning was added to Chrome, Chrome Android, and Edge in Chromium 125, so the polyfill will not be applied to versions after 124. Some aspects of anchor positioning were shipped later in Chromium, meaning that they are not polyfilled and are not present in those versions.

  • position-try-fallbacks was added in 128 after being renamed from position-try-order. Use both -fallbacks and -order or the position-try shorthand to make sure all versions are covered.
  • position-area was added in 129.
  • anchor-scope was added in 131.

Getting Started

To use the polyfill, add this script tag to your document <head>:

<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>

If you want to manually apply the polyfill, you can instead import the polyfill function directly from the @oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js file.

For build tools such as Vite, Webpack, and Parcel, that will look like this:

import polyfill from '@oddbird/css-anchor-positioning/fn';

polyfill();

The polyfill function returns a promise that resolves when the polyfill has been applied.

Constructed stylesheets (adoptedStyleSheets)

If your custom elements use constructed stylesheets (via new CSSStyleSheet() + replaceSync() + shadowRoot.adoptedStyleSheets), call patchAndPolyfillConstructedStylesheets() before any custom element's connectedCallback runs:

<script type="module">
  if (!('anchorName' in document.documentElement.style)) {
    const { patchAndPolyfillConstructedStylesheets } =
      await import('https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js');
    patchAndPolyfillConstructedStylesheets();
    // Define your custom elements after the patch has applied the polyfill.
    // You don't need to explicitly call polyfill().
  }
</script>

With a bundler:

import { patchAndPolyfillConstructedStylesheets } from '@oddbird/css-anchor-positioning/fn';

patchAndPolyfillConstructedStylesheets();

This patches CSSStyleSheet.prototype.replaceSync to capture stylesheet source text, and patches the ShadowRoot.prototype.adoptedStyleSheets setter to automatically run the polyfill for each shadow root once its host element's connectedCallback finishes.

You can view a more complete demo here.

Configuration

The polyfill supports a small number of options. When using the default version of the polyfill that executes automatically, options can be set by setting the value of window.ANCHOR_POSITIONING_POLYFILL_OPTIONS.

<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    window.ANCHOR_POSITIONING_POLYFILL_OPTIONS = {
      elements: undefined,
      excludeInlineStyles: false,
      positionAreaContainingBlock: true,
      roots: [document],
      useAnimationFrame: false,
    };
    import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>

When manually applying the polyfill, options can be set by passing an object as an argument.

<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    const { default: polyfill } = await import("https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js");

    polyfill({
      elements: undefined,
      excludeInlineStyles: false,
      positionAreaContainingBlock: true,
      roots: [document],
      useAnimationFrame: false,
    });
  }
</script>

elements

type: HTMLElements[], default: undefined

If set, the polyfill will only be applied to the specified elements instead of to all styles. Any specified <link> and <style> elements will be polyfilled. By default, all inline styles in the document will also be polyfilled, but if excludeInlineStyles is true, only inline styles on specified elements will be polyfilled.

excludeInlineStyles

type: boolean, default: false

When not defined or set to false, the polyfill will be applied to all elements that have eligible inline styles, regardless of whether the elements option is defined. When set to true, elements with eligible inline styles listed in the elements option will still be polyfilled, but no other elements in the document will be implicitly polyfilled.

roots

type: (Document | HTMLElement | ShadowRoot)[], default: [document]

By default the polyfill applies to document, but you can configure one or more shadow roots the polyfill should apply to using this option. See the shadow DOM examples to learn more.

useAnimationFrame

type: boolean, default: false

Determines whether anchor calculations should update on every animation frame (e.g. when the anchor element is animated using transforms), in addition to always updating on scroll/resize. While this option is optimized for performance, it should be used sparingly.

For legacy support, this option can also be set by setting the value of window.UPDATE_ANCHOR_ON_ANIMATION_FRAME, or, when applying the polyfill manually, by passing a single boolean with polyfill(true).

positionAreaContainingBlock

type: boolean | 'auto', default: true

Controls how the polyfill emulates the containing block that position-area natively creates for a target.

  • true (default): the polyfill wraps each position-area target with an element that approximates the grid-area containing block, and aligns the target within it. This matches the native behavior most closely, but the extra wrapper element can interfere with author CSS that depends on the target's position in the DOM tree (for example direct-child or sibling combinators, or flex/grid layout of the target's original parent).

  • false: the polyfill never adds a wrapper. Instead it computes and applies inset values directly on the target. This avoids the extra element, but styles that resolve against the containing block — percentage sizes, auto or percentage margins, percentage padding, or stretch/anchor-center self-alignment — will not match the native behavior.

  • 'auto': the polyfill adds the wrapper only for targets whose styles resolve against the containing block (using the same heuristics listed above), and positions all other targets directly. This keeps the wrapper's correctness where it matters while avoiding the extra element for the common case.

    Two caveats apply to 'auto':

    • The wrap decision is made from a target's base styles when the polyfill runs. Containing-block-dependent styles that appear only in a @position-try fallback block are not detected, so a target that becomes containing-block dependent only in a fallback may be positioned without the wrapper it needs.
    • The decision is computed once and not re-evaluated. If author CSS later toggles a containing-block-dependent style on a target (for example by adding a class with a percentage size or auto margin), the target is not re-wrapped. Use true when a target's containing-block dependence can change at runtime.

Limitations

While this polyfill supports many basic use cases, it doesn't (yet) support the following features:

  • The following portions of Position Fallback:
    • position-try-order. If try-size is specified in position-try shorthand, it will be parsed, and try-tactics will be applied, but the try-size will be ignored.
    • The flip-start try-tactic is only partially supported. The tactic is only applied to property names and anchor sides.
    • a position-area as a try-tactic
    • Fallback does not support percentage anchor-side values, nor anchor functions that are passed through custom properties.
  • Polyfill allows anchoring in scroll more permissively than the spec allows, for instance without a default position-anchor.
  • anchor-scope property on pseudo-elements
  • The polyfill shifts not-yet-supported properties (e.g. anchor-name, anchor-scope) into custom properties, which it makes non-inherited so they mirror the non-inherited behavior of the properties they stand in for -- via CSS.registerProperty (Safari 16.4+, Firefox 128+), or, on older engines, a universal initial reset injected once per polyfilled root. This reaches every root the polyfill reads these properties from (document and any shadow roots passed in the roots option).
  • anchor-center value for justify-self, align-self, justify-items, and align-items properties
  • position-visibility property
  • dynamically added/removed anchors or targets
  • anchors and targets in separate shadow roots (see #191)
  • vertical/rtl writing-modes for anchor functions (partial support)
  • implicit anchors or the position-anchor: auto keyword (pending resolution of whatwg/html#9144)
  • position-area support has a few differences from native behavior:
    • Overflow alignment is not applied for a target that overflows its inset-modified containing block but would still fit within its original containing block. In other words, a polyfilled target may be placed in a position-area grid section outside its containing block, where the implementation would move the target inside the containing block.
    • When the polyfill emulates the containing block by adding a wrapping element around the target (see the positionAreaContainingBlock option), this adds further differences:
      • It breaks selectors that rely on a direct relationship with the target, for instance ~ target, + target, > target or using :nth selectors.
      • For popover targets, the browser promotes the element to the top layer when it is shown, which makes the viewport (not the wrapper) its containing block. To work around this, the polyfill strips any non-auto inset from the target (setting inset: auto) and re-applies it as padding on the wrapper, so the wrapper continues to drive positioning.
    • When the wrapper is not added, styles that resolve against the containing block — percentage sizes, auto or percentage margins, percentage padding, or stretch/anchor-center self-alignment — will not match native behavior.

In addition, JS APIs like CSSPositionTryRule or CSS.supports will not be polyfilled.

Inline styles

Browsers provide some validation for imperatively setting inline styles. el.style.color = "foo" and el.style.foo = "bar" do not change the inline styles of el. This is problematic for this polyfill, as we would like to support el.style.anchorName = "--foo", but that won't work in browsers that don't support the anchor-name property.

While el.setAttribute('style', 'anchor-name: --foo') or <div style="anchor-name: --foo" /> both work, developers are often using tools that generate the DOM. Both React and Vue use methods that remove the unknown inline style properties at runtime.

If you are using inline styles to set anchor-related properties and the polyfill isn't working, verify that the inline styles are actually showing up in the DOM.

Invalid CSS

Some types of invalid CSS will cause the polyfill to throw an error. In these cases, the polyfill will report any parse errors encountered in the console as warnings. This will be followed by the error thrown by the polyfill.

The polyfill can't determine which parse error caused the polyfill error, but please resolve any reported parse errors before opening a bug. We also recommend using a CSS linter like Stylelint or @eslint/css.

Sponsor OddBird's OSS Work

At OddBird, we love contributing to the languages & tools developers rely on. We're currently working on polyfills for new Popover & Anchor Positioning functionality, as well as CSS specifications for functions, mixins, and responsive typography. Help us keep this work sustainable and centered on your needs as a developer! We display sponsor logos and avatars on our website.

Sponsor OddBird's OSS Work

Releases

Sponsor this project

Used by

Contributors

Languages