Skip to content
uiify

NativePopover

Trigger, surface and close parts around the native Popover API. 0 kB client JS, at most 0.5 kB gzipped.

Anatomy

<NativePopover.Trigger target="actions">Actions</NativePopover.Trigger>
<NativePopover.Surface id="actions">
  <NativePopover.Close target="actions">Close</NativePopover.Close>
</NativePopover.Surface>

Live demo

This demo also runs with JavaScript disabled.

Anchored below the trigger.

NativePopover.Trigger

Renders <button> (via `createElement`). Emits popoverTarget, popoverTargetAction, type="button", class="uiify-native-popover-trigger".

PropTypeDefaultDescription
targetstring—Required. `id` of the `NativePopover.Surface` this button acts on.
action"toggle" | "show" | "hide""toggle"What this trigger does to its target popover when activated.
asNativeButtonAs<TAs>"button"See NativeInvoker.

NativePopover.Surface

Renders <div popover>. Emits popover={mode}, data-side, data-align, data-positioning="native", class="uiify-native-popover-surface".

PropTypeDefaultDescription
idstring—Required. Referenced by trigger and close `target`.
mode"auto" | "manual""auto"The cross-engine popover baseline; `hint` is not part of this contract.
side"top" | "right" | "bottom" | "left"—Anchor side.
align"start" | "center" | "end"—Anchor alignment.

NativePopover.Close

Renders <button> (via `createElement`). Emits popoverTargetAction="hide", class="uiify-native-popover-close".

PropTypeDefaultDescription
targetstring—Required. `id` of the `NativePopover.Surface` this button hides.
asNativeButtonAs<TAs>"button"See NativeInvoker.

Rendered DOM

<button popoverTarget="actions" popoverTargetAction="toggle" type="button" class="uiify-native-popover-trigger">Actions</button><div data-align="center" data-positioning="native" data-side="bottom" popover="auto" id="actions" class="uiify-native-popover-surface"><button popoverTarget="actions" popoverTargetAction="hide" type="button" class="uiify-native-popover-close">Close</button></div>

CSS contract

[popover]:popover-open {
  /* true while the surface is shown */
}

/* the element sets data-side/data-align from side/align — style off these, not JS */

Build your own

import { NativePopover } from "@gunkin/uiify/elements/native-popover";

export function ActionMenu() {
  return (
    <>
      <NativePopover.Trigger target="row-actions">Actions</NativePopover.Trigger>
      <NativePopover.Surface aria-label="Row actions" id="row-actions">
        <button type="button">Rename</button>
        <button type="button">Duplicate</button>
        <button type="button">Delete</button>
      </NativePopover.Surface>
    </>
  );
}

Used by

Gotchas

  • `id` must be stable across server and client renders — `NativePopover.Trigger`/`NativePopover.Close`'s `target` has to match it exactly.
  • `NativePopover.Surface` has no implicit ARIA role, so — unlike `NativeDialog.Content` — it has no required accessible name.
  • `renderToStaticMarkup` keeps `popoverTarget`/`popoverTargetAction` camelCase in the raw HTML string (React doesn't special-case them for static rendering the way it does `command`/`commandfor`) — real browser behavior and hydration are unaffected, but don't be surprised reading the SSR output.

Import

import { NativePopover } from "@gunkin/uiify/elements/native-popover";