{"record":{"id":"d1aca18386af64b0","repo":"remix-run/remix","slug":"popover-surface-requires-a-registered-anchor-bef","errorCode":null,"errorMessage":"popover.surface() requires a registered anchor before opening","messagePattern":"popover\\.surface\\(\\) requires a registered anchor before opening","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/src/popover/index.ts","lineNumber":106,"sourceCode":"      let wasOpen = openProp\n      openProp = options.open\n\n      handle.queueTask(async (node) => {\n        if (openProp && !wasOpen) {\n          node.showPopover()\n        } else if (!openProp && wasOpen) {\n          node.hidePopover()\n        }\n      })\n\n      return [\n        attrs({ popover: 'manual' }),\n\n        on('beforetoggle', (event) => {\n          if (event.newState === 'open') {\n            let anchor = context.anchor\n            if (!anchor) {\n              throw new Error('popover.surface() requires a registered anchor before opening')\n            }\n\n            cleanupAnchor = positionAnchor(event.currentTarget, anchor.target, anchor.options)\n            unlockScroll = lockScroll()\n          } else if (event.newState === 'closed') {\n            cleanupAnchor()\n            unlockScroll()\n          }\n        }),\n\n        on('toggle', async (event) => {\n          if (event.newState === 'open') {\n            context.showFocusTarget?.focus()\n          } else if (event.newState === 'closed' && options.restoreFocusOnHide !== false) {\n            context.hideFocusTarget?.focus()\n          }\n        }),\n","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/ui/src/popover/index.ts#L88-L124","documentation":"The popover surface mixin opens the native Popover API element in response to an `open` prop, but before it can position itself it needs an anchor registered via `popover.anchor()` (which stores `{ target, options }` on the shared PopoverProvider context). When the `beforetoggle` event fires with newState 'open' and `context.anchor` is null, the mixin throws because there is no element to position the surface against.","triggerScenarios":"Rendering a `popover.surface({ open: true, ... })` element without a sibling/ancestor element that applies `popover.anchor(options)` inside the same popover provider context. Also occurs if the anchor element is conditionally not rendered (e.g. anchor branch skipped) while open is true, or the surface is used outside a popover root so context.anchor was never set.","commonSituations":"Copy-pasting a popover surface markup without the trigger's anchor mixin; toggling `open` to true before the anchor element mounts (conditional rendering race); refactoring so the anchor and surface no longer share the popover context provider.","solutions":["Add `popover.anchor()` to the trigger element inside the same popover context, e.g. `mix={[popover.anchor()]}` on the button that toggles the popover","Ensure the anchor element is rendered whenever `open` can be true — don't conditionally unmount the anchor while keeping the surface open","Keep the anchor and surface under the same popover provider component so `handle.context.get(PopoverProvider)` resolves to the same instance"],"exampleFix":"// before\n<button mix={[on('click', () => { open = true; handle.update() })]}>Menu</button>\n<div mix={[popover.surface({ open, onHide })]}>…</div>\n\n// after\n<button mix={[popover.anchor(), on('click', () => { open = true; handle.update() })]}>Menu</button>\n<div mix={[popover.surface({ open, onHide })]}>…</div>","handlingStrategy":"validation","validationCode":"// Before setting open=true, ensure the anchor mixin has been applied in the same\n// popover context — structurally: check that your trigger element includes\n// popover.anchor() and is rendered:\nif (open && !anchorMounted) {\n  // render the trigger/anchor first, then open\n  anchorMounted = true\n  handle.update()\n  handle.queueTask(() => { open = true; handle.update() })\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always author the anchor trigger and the surface together as one popover component so they can't diverge","Never conditionally render the anchor while `open` is true","Add a test that toggles open immediately after mount to catch missing-anchor regressions"],"tags":["popover","ui","anchor","positioning","remix-ui"],"backgroundTag":"missing-required-anchor-element","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}