Components

Popover

Provide lightweight anchored floating content with deterministic non-modal dismissal semantics.

Release Scope: Canonical Implementation: Core Site Visibility: Listed Adapters: React, Vanilla, Solid, Vue, Svelte Category: overlay

1. Live Preview

vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.

vanilla popover preview activates on the client.

2. Installation

Install adapter-specific dependencies first, then complete setup via the installation guide. installation docs

pnpm add @nake-ui/primitives @nake-ui/adapter-vanilla @nake-ui/theme-vanilla

3. Usage

Minimal runnable usage for the current adapter.

import "@nake-ui/theme-vanilla/styles.css";
import { bindPopover } from "@nake-ui/adapter-vanilla";

const binding = bindPopover(nodes, items, options);
binding.destroy();

4. Examples

Align

Show start / center / end cross-axis placement with aligned popover positioners.

import "@nake-ui/theme-vanilla/styles.css";
import { bindPopover } from "@nake-ui/adapter-vanilla";

const aligns = ["start", "center", "end"] as const;

for (const align of aligns) {
  const binding = bindPopover(
    { root, trigger, positioner, content, title, description },
    { id: `example-popover-${align}`, defaultOpen: false, placement: "bottom", align, matchAnchorWidth: false }
  );

  // cleanup on unmount
  binding.destroy();
}

trigger.textContent = "Align: start";

Custom

Render a custom profile card while preserving popover title, description, and dialog semantics.

import "@nake-ui/theme-vanilla/styles.css";
import { bindPopover } from "@nake-ui/adapter-vanilla";

if (typeof window !== "undefined" && typeof document !== "undefined") {
  const binding = bindPopover(
    { root, trigger, positioner, content, title, description },
    { id: "example-custom-popover", defaultOpen: false, matchAnchorWidth: false }
  );

  // cleanup on unmount
  binding.destroy();
}

trigger.textContent = "Open profile card";

5. RTL

The same structure running inside a `dir="rtl"` container.

vanilla popover preview activates on the client.

6. Closure Coverage

Complete

Closure Score: 100% (19/19)

Required Checks

  • schemaRegistered
  • specDocumented
  • reactMapper
  • siteExampleCoverage
  • siteA11yCoverage
  • siteVisible
  • schemaValidatorCoverage
  • schemaAriaCoverage
  • specValidatorRulesSection
  • specTestChecklistSection
  • specSsrHydrationSection
  • reactSsrHydrationCoverage
  • reactRenderLevelSsrCoverage
  • solidSsrCoverage
  • coreImplemented
  • primitiveUnitTest
  • domContractSnapshot
  • vanillaBinding
  • adapterParitySnapshot

All required closure checks are currently satisfied.

7. Purpose / Non-goals

Provide lightweight anchored floating content with deterministic non-modal dismissal semantics.

  • Modal focus trap and focus restoration policy by default
  • Floating transition orchestration and visual animation ownership
  • Auto-placement animations, arrows, and popover theming

8. Anatomy & Slot

  • root (optional structural wrapper in Vue)
  • anchor (optional positioning anchor)
  • trigger
  • positioner (optional floating wrapper)
  • content
  • title (optional)
  • description (optional)

9. DOM Contract

<div data-ui="popover" data-slot="root" data-state="open">
  <button
    id="popover-trigger"
    data-ui="popover"
    data-slot="trigger"
    data-state="open"
    aria-haspopup="dialog"
    aria-expanded="true"
    aria-controls="popover-content"
  ></button>
  <div data-slot="positioner" data-state="open" data-placement="bottom-start" data-side="bottom">
    <div
      id="popover-content"
      data-slot="content"
      data-state="open"
      role="dialog"
      aria-labelledby="popover-content-title"
      aria-describedby="popover-content-description"
      hidden=""
    ></div>
  </div>
</div>

10. State & Events

State Model

  • open and defaultOpen
  • controlled via open + onOpenChange
  • uncontrolled internal state when open is omitted
  • close / openPopover / toggle request helpers for hook/compound usage
  • anchor and content node linkage for layer dismissal behavior
  • title/description IDs bound to aria-labelledby / aria-describedby

Event Model

  • trigger click toggles open/close
  • controlled/uncontrolled flows preserve request semantics
  • outside pointer/focus dismiss route through dismissable layer
  • Escape dismisses active popover layer

11. Keyboard & Focus

  • native host semantics for keyboard are retained outside component boundaries
  • Escape closes the layer and returns control to native focus path
  • no mandatory focus trapping in this primitive

12. ARIA

  • trigger: aria-haspopup=&quot;dialog&quot;, aria-expanded, aria-controls
  • content: role=&quot;dialog&quot;, aria-labelledby, aria-describedby, hidden

13. Validator Rules

  • structure.invalid-slot-placement
  • structure.missing-required-slot
  • aria.broken-controls-linkage
  • aria.missing-label
  • keyboard.unhandled-escape
  • state.invalid-open-state

14. Adapter Notes

Vanilla

  • Primary binder: bindPopover.
  • Binds explicit DOM nodes and should be cleaned up on unmount.

15. Example Mapping

16. API

Core

  • createPopoverController value
  • PopoverController type
  • PopoverControllerOptions type

Vanilla

  • bindPopover function
  • PopoverBindingOptions interface
  • PopoverElements interface
  • PopoverPositioningAlign type
  • PopoverPositioningPlacement type