Skip to content
VEXA
Overlays

Popover

Client leaf

A non-modal floating panel anchored to a trigger.

Live preview

Rendered from the installed VEXA source — the same files you receive.

Installation

Adds the component source to your project. Registry dependencies are pulled in automatically.

npx shadcn@latest add https://vexa.valfiguer.com/r/popover.json

Installs to components/vexa/popover.tsx

npm dependencies

  • @radix-ui/react-popover

Registry dependencies

  • @vexa/utils

Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.

Source

Copy this into its target path, or install it with the command above.

components/vexa/popover.tsx
"use client";

import type * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";

import { cn } from "@/lib/utils";

/**
 * Popover — a non-modal floating panel anchored to a trigger.
 *
 * Client leaf. Radix manages positioning, outside-click and Escape dismissal,
 * and returns focus to the trigger on close. Unlike a Dialog it does not trap
 * focus or make the page inert — use it for rich, non-critical content
 * (filters, pickers, help). Give the content an accessible name when it has no
 * heading.
 */
export function Popover(
  props: React.ComponentProps<typeof PopoverPrimitive.Root>,
) {
  return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}

export function PopoverTrigger(
  props: React.ComponentProps<typeof PopoverPrimitive.Trigger>,
) {
  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}

export function PopoverAnchor(
  props: React.ComponentProps<typeof PopoverPrimitive.Anchor>,
) {
  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}

export function PopoverContent({
  className,
  align = "center",
  sideOffset = 6,
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
  return (
    <PopoverPrimitive.Portal>
      <PopoverPrimitive.Content
        data-slot="popover-content"
        align={align}
        sideOffset={sideOffset}
        className={cn(
          "z-50 w-72 rounded-vexa-md border border-vexa-border bg-vexa-surface-raised p-4 text-vexa-foreground shadow-vexa-lg outline-none",
          "transition-[opacity,transform] duration-[var(--vexa-duration-fast)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
          "data-[state=closed]:scale-95 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100",
          className,
        )}
        {...props}
      />
    </PopoverPrimitive.Portal>
  );
}

Details

Example

tsx
<Popover><PopoverTrigger asChild><Button variant="outline">Filters</Button></PopoverTrigger><PopoverContent>...</PopoverContent></Popover>

Accessibility

Non-modal: outside-click and Escape dismiss it and focus returns to the trigger; it does not trap focus or make the page inert. Give the content an accessible name when it has no heading.