{"componentChunkName":"component---src-templates-post-jsx","path":"/blog/css-only-floating-focus-with-anchor-positioning/","result":{"data":{"mdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nconst _frontmatter = {\n  \"title\": \"CSS-only floating focus with anchor positioning\",\n  \"cover\": \"/blogs/focusanchor/cover.svg\",\n  \"date\": \"2025-09-16\",\n  \"type\": [\"tutorial\"]\n};\nconst layoutProps = {\n  _frontmatter\n};\nconst MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  let {\n    components\n  } = _ref,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, `The Track focus debug tool in Polypane shows a floating outline that follows the keyboard focus around the page. This makes it easier to keep track of where the focus is and lets you determine (along with the focus overlay in Polypane) if the focus order makes sense.`), mdx(\"video\", {\n    src: trackfocus,\n    controls: true,\n    muted: true,\n    playsInline: true,\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `Our debug tool uses JavaScript. With CSS Anchor positioning we can get the same effect without any JavaScript at all, so let's see how that works.`), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, `The Track focus debug tool is based on the `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://engineering.q42.nl/floating-focus/\"\n  }), `floating focus`), ` concept by Guido Bouman at `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://q42.nl/\"\n  }), `Q42`), `. Be sure to read his article, which also includes accessibility research.`)), mdx(\"h2\", {\n    \"id\": \"why-css-only\"\n  }, `Why CSS-only?`), mdx(\"p\", null, `Our JavaScript implementation works well, but measuring and keeping track of elements on the JavaScript main thread is not ideal, since it takes the browser extra work and could interfere with the JS already on the page.`), mdx(\"p\", null, `Using only CSS means all the work is offloaded to the browser's rendering engine, which can optimize it better. That means less CPU usage, better battery life and smoother animations.`), mdx(\"h2\", {\n    \"id\": \"css-anchor-positioning\"\n  }, `CSS Anchor positioning`), mdx(\"p\", null, `CSS Anchor positioning allows you to position elements relative to other elements on the page. It's supported in Chromium (Polypane included) and Safari as of version 26, provided the elements are keyboard focusable. Support in Firefox is underway.`), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, `In Safari, button elements are not keyboard focusable by default. Users must enable this in Safari's settings under Advanced > \"Press Tab to highlight each item on a webpage\". On your own pages, you can make elements keyboard focusable by adding a `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `tabindex=\"0\"`), ` attribute to them. We have done so in the demo below, but be aware that users might not expect this behavior if they're used to Safari's default.`)), mdx(\"p\", null, `This post isn't a tutorial on how to use CSS Anchor positioning, since there are quite a few parts of it that we don't need for our focus tracking. If you want to learn more about Anchor positioning, here are some great resources:`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://ishadeed.com/article/anchor-positioning/\"\n  }), `The Basics of Anchor Positioning by Ahmad Shadeed`)), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://developer.chrome.com/blog/anchor-positioning-api\"\n  }), `Introducing the CSS anchor positioning API by Una Kravets for the Chrome Developers blog`)), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://css-tricks.com/one-of-those-onboarding-uis-with-anchor-positioning/\"\n  }), ` One of Those “Onboarding” UIs, With Anchor Positioning by Ryan Trimble `))), mdx(\"h2\", {\n    \"id\": \"setting-up-focus-tracking-with-css-anchor-positioning\"\n  }, `Setting up focus tracking with CSS Anchor positioning`), mdx(\"p\", null, `For CSS Anchor positioning, we need two things:`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, `An element that anchors itself.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, `An element that the first element is anchored to.`)), mdx(\"p\", null, `Linking these two element is done by setting an `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `anchor-name`), ` on the element that can be anchored to, and referencing that in the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `position-anchor`), ` property on the element that does the anchoring.`), mdx(\"p\", null, `If we want to have an indicator that follows the focus, we need to set the anchor-name on whatever element has the focus:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `*:focus`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `anchor-name`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `That's all the CSS we need for the element that can be anchored to.`), mdx(\"p\", null, `Now we need to create the element that will follow the focus. In our JavaScript implementation we use a custom web component for this. For this CSS-only version, we're going to use the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `::before`), ` pseudo-element on the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `html`), `, so that we don't need to add any extra elements to the page:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `content`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token string\"\n  }), `''`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` fixed`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `This by itself doesn't do anything, because though we've set and referenced an anchor, we haven't yet told it `, mdx(\"em\", {\n    parentName: \"p\"\n  }, `how`), ` to anchor itself. For that, we use the inset properties with the new `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `anchor()`), ` function:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `content`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token string\"\n  }), `''`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` fixed`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `inset`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token function\"\n  }), `anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), `inside`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `The `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `anchor()`), ` function returns positioning from the element that you're anchored to. The `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `inside`), ` keyword is a shorthand that sets all four inset properties (`, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `top`), `, `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `right`), `, `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `bottom`), `, and `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `left`), `) to the corresponding values from the anchored element.`), mdx(\"p\", null, `It's nice if the focus indicator is slightly bigger than the focused element, so it surrounds it without overlapping it.`), mdx(\"p\", null, `While we can use the anchor function in a calc by setting `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `top: calc(anchor(top) - 6px)`), `, `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `right: calc(anchor(right) - 6px)`), ` and so on, it's easier to use a negative margin to achieve the same effect on all sides at once:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `content`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token string\"\n  }), `''`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` fixed`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `inset`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token function\"\n  }), `anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), `inside`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `margin`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` -6px`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `You still won't see anything, because we haven't given our focus indicator any style. Let's add some styling to make it visible:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `content`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token string\"\n  }), `''`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` fixed`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `inset`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token function\"\n  }), `anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), `inside`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `margin`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` -6px`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `display`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` block`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `background`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` #ff02`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `border`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 2px solid #3d80a2`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `border-radius`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 6px`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `pointer-events`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` none`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `z-index`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 2147483647`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `transition`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` inset 0.2s linear`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `This gives our focus indicator a semi-transparent background and a border. The `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `pointer-events: none`), ` ensures that it doesn't interfere with clicking or focusing elements on the page. The `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `z-index`), ` is set as high as possible to ensure it's on top of other elements. Lastly we add a transition to the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `inset`), ` property, which will animate the movement of the pseudo-element when the focus changes.`), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `2147483647`), ` for the z-index seems an arbitrary number, but it's actually the highest possible value for a 32-bit signed integer, which is what browsers use for z-index values.`)), mdx(\"h2\", {\n    \"id\": \"hiding-the-indicator-when-theres-no-focus\"\n  }, `Hiding the indicator when there's no focus`), mdx(\"p\", null, `When an element isn't anchored (in our case, when there are no focused elements), the element is unfortunately still visible because even though it has no width and height, it still has a border.`), mdx(\"p\", null, `As of now there is no way to show anchor elements only when they're anchored, so we need to find another workaround.`), mdx(\"p\", null, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.frontend.fyi/\"\n  }), `Jeroen`), ` suggested using `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `:focus-within`), `, since that will tell us if there is `, mdx(\"em\", {\n    parentName: \"p\"\n  }, `any`), ` element that's focused. By combining it with `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `:not()`), `, we can select the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `::before`), ` pseudo-element when there are no focused elements and hide it:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html:not(:focus-within)::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `opacity`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 0`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"h2\", {\n    \"id\": \"hiding-the-default-focus-outline-progressively-enhanced\"\n  }, `Hiding the default focus outline, progressively enhanced`), mdx(\"p\", null, `Browsers by default give focused elements an outline, and it's part of your site that you can also implicitly design (by styling `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `:focus-visible`), ` in a way that fits your design). Now that we built our own focus indicator, we want to hide that default outline.`), mdx(\"p\", null, `Since CSS Anchor positioning isn't supported everywhere yet, we should make sure that we only replace that default outline with our floating one in browsers that have support. We can do that by only hiding the outline when `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `position-anchor`), ` is supported:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token atrule\"\n  }), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token rule\"\n  }), `@supports`), ` `, mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --test`, mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`)), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `*:focus`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `outline`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` none`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `And while we're at it, we can also wrap all the other CSS in that `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `@supports`), ` rule, so that none of it is applied in browsers that don't support it. Thanks `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://krijnhoetmer.nl/\"\n  }), `Krijn`), ` for suggesting I add the progressive enhancement logic!`), mdx(\"p\", null, `There is also a `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://anchor-positioning.oddbird.net/\"\n  }), `Anchor Positioning Polyfill`), ` available that you can include on your site to add support for browsers that don't support it natively yet.`), mdx(\"h2\", {\n    \"id\": \"result-and-demo\"\n  }, `Result and Demo`), mdx(\"p\", null, `And that's it! Here's all the CSS together:`), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"css\"\n  }, mdx(\"pre\", _extends({\n    parentName: \"div\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token atrule\"\n  }), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token rule\"\n  }), `@supports`), ` `, mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --test`, mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`)), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `*:focus`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `anchor-name`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `outline`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` none`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `content`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token string\"\n  }), `''`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` fixed`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `position-anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` --focus-anchor`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `inset`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token function\"\n  }), `anchor`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `(`), `inside`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `)`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `margin`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` -6px`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `display`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` block`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `background`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` #ff02`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `border`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 2px solid #3d80a2`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `border-radius`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 6px`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `pointer-events`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` none`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `z-index`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 2147483647`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `opacity`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 1`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `transition`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` inset 0.2s linear`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`), `\n\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token selector\"\n  }), `html:not(:focus-within)::before`), ` `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `{`), `\n    `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token property\"\n  }), `opacity`), mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `:`), ` 0`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `;`), `\n  `, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`), `\n`, mdx(\"span\", _extends({\n    parentName: \"code\"\n  }, {\n    \"className\": \"token punctuation\"\n  }), `}`)))), mdx(\"p\", null, `With just those few lines of CSS, we have a floating focus indicator that follows the keyboard focus around the page.`), mdx(\"p\", null, `Try it out below (In Polypane, Chromium or Safari) by clicking one of the buttons and then using the Tab key to move the focus around:`), mdx(\"div\", {\n    className: \"focus-demo-container-wrap\"\n  }, mdx(\"div\", {\n    className: \"focus-demo-container\"\n  }, mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 1\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 2\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 3\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 4\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 5\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 6\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 7\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 8\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 9\"), mdx(\"button\", {\n    tabIndex: \"0\"\n  }, \"Button 10\"))), mdx(\"p\", null, `Or try it out on `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://codepen.io/Kilian/pen/ZYbgBxx\"\n  }), `CodePen`), `:`), mdx(\"iframe\", {\n    height: \"300\",\n    style: {\n      \"width\": \"100%\",\n      \"maxWidth\": \"100%\"\n    },\n    scrolling: \"no\",\n    title: \"css track focus\",\n    src: \"https://codepen.io/Kilian/embed/ZYbgBxx?default-tab=css%2Cresult\",\n    frameBorder: \"no\",\n    loading: \"lazy\",\n    allowtransparency: \"true\",\n    allowFullScreen: \"true\"\n  }, \"See the Pen \", mdx(\"a\", {\n    href: \"https://codepen.io/Kilian/pen/ZYbgBxx\"\n  }, \"css track focus\"), \" by Kilian Valkhof (\", mdx(\"a\", {\n    href: \"https://codepen.io/Kilian\"\n  }, \"@Kilian\"), \") on \", mdx(\"a\", {\n    href: \"https://codepen.io\"\n  }, \"CodePen\"), \".\"));\n}\n;\nMDXContent.isMDXComponent = true;","timeToRead":4,"tableOfContents":{"items":[{"url":"#why-css-only","title":"Why CSS-only?"},{"url":"#css-anchor-positioning","title":"CSS Anchor positioning"},{"url":"#setting-up-focus-tracking-with-css-anchor-positioning","title":"Setting up focus tracking with CSS Anchor positioning"},{"url":"#hiding-the-indicator-when-theres-no-focus","title":"Hiding the indicator when there's no focus"},{"url":"#hiding-the-default-focus-outline-progressively-enhanced","title":"Hiding the default focus outline, progressively enhanced"},{"url":"#result-and-demo","title":"Result and Demo"}]},"excerpt":"The Track focus debug tool in Polypane shows a floating outline that follows the keyboard focus around the page. This makes it easier to keep track of where the…","frontmatter":{"title":"CSS-only floating focus with anchor positioning","cover":"/blogs/focusanchor/cover.svg","date":"2025-09-16","updated":null},"fields":{"slug":"/blog/css-only-floating-focus-with-anchor-positioning/","date":"2025-09-15T22:00:00.000Z","ogFileName":"css-only-floating-focus-with-anchor-positioning"}}},"pageContext":{"slug":"/blog/css-only-floating-focus-with-anchor-positioning/"}},"staticQueryHashes":["4164364741","425175329"]}