NativePopover.ref is the smallest bridge from { open: boolean } to the browser Popover API. Use it when an existing semantic element needs native top-layer visibility and you own the rest of the interaction. It does not set popover, assign a role, position content, or listen for browser state changes.
Prerequisites: Popover for the complete Typed family and Dom refs for scoped element integration. The MDN Popover API explains native auto/manual behavior; selecting a mode belongs to the element’s markup.
Keep an auto popover synchronized
Pass a stable, page-unique ID for the target relationship. This application uses native auto dismissal and declarative targeting. The toggle handler records the browser’s final state; the external button also demonstrates opening through Typed state.
import { RefSubject } from "@typed/fx";
import { EventHandler, html, component } from "@typed/template";
import * as Dom from "@typed/ui/Dom";
import * as NativePopover from "@typed/ui/NativePopover";
const ExportHelp = component(function* (id: string) {
const state = yield* RefSubject.make({ open: false });
const readToggle = EventHandler.make((event: Event) => {
const open = Dom.toggleState(event) === "open";
return RefSubject.set(state, { open });
});
return html`
<button type="button" popovertarget=${id}>Export help</button>
<button type="button" onclick=${RefSubject.set(state, { open: true })}>Show help</button>
<aside id=${id} popover="auto" aria-label="Export help"
ref=${NativePopover.ref(state)} ontoggle=${readToggle}>
<p>CSV includes the currently visible rows and columns.</p>
<button type="button" popovertarget=${id} popovertargetaction="hide">Close</button>
</aside>
`;
});
The observer checks element.matches(":popover-open") before calling showPopover() or hidePopover(). This makes repeated equal desired states harmless at the native boundary. It does not deduce whether the user intended to dismiss; ontoggle supplies that reverse direction. If you omit it, outside dismissal can leave state true and a later state emission can show the surface again.
Choose the smaller primitive deliberately
Unlike the compound Popover, this example can select popover="auto" because it owns the host. Auto dismissal is supplied by the browser, not by the ref. The standard family fixes manual as an internal prop, so attempting to override that through props is not the same extension point.
State observation belongs to the ref’s Scope. Closing that Scope stops synchronization but does not call hidePopover on externally owned elements.
The observer supplies no SSR attributes or serialized state. For server-rendered markup, see Dom ref composition to attach a hydration owner alongside the observer.
Debug the native boundary
An initially open state waits for the host to connect before invoking showPopover(). A later
closed state or Scope teardown cancels that pending wait, so a removed or deliberately closed
surface cannot open merely because it is attached later. This follows the same scoped connection
policy as NativeDialog; hidden documents may defer the check until animation frames resume.
A missing popover attribute can still make the native call invalid. Missing showPopover indicates that the environment does not supply the API; this primitive does not polyfill it. Exceptions in native methods are defects, while state failures retain their original typed E. Test against real toggle events, including an outside click for auto mode and repeated open/close cycles.
For a complete supporting panel, use Popover; for interactive pointer/focus previews, use Hovercard. API: NativePopover.ref.