Aura Design System

Popover

Displays rich content in a portal triggered by a button.

Preview

import { useState } from "react";
import {
  Popover,
  PopoverTrigger,
  PopoverContent,
  PopoverClose,
  PopoverArrow,
  PopoverAnchor,
} from "@/components/ui/Popover";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Label } from "@/components/ui/Label";

export function PopoverDemo() {
  return (
  <Popover>
    <PopoverTrigger asChild>
      <Button variant="menu">Open Popover</Button>
    </PopoverTrigger>
    <PopoverContent className="w-30 p-1">
      <div className="space-y-1">
        <h4 className="font-medium leading-none">Dimensions</h4>
        <p className="text-sm text-gray-11">
          Set the dimensions for the layer.
        </p>
      </div>
    </PopoverContent>
  </Popover>
)
}

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/popover

Manual

Install the following dependencies:

pnpm install @radix-ui/react-icons radix-ui

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the Popover component into your components/ui/Popover.tsx file.

"use client";
/**
 * @description Displays rich content in a portal triggered by a button.
 */
import * as React from "react";
import { Popover as PopoverRadix } from "radix-ui";
import { Cross2Icon } from "@radix-ui/react-icons";

import { cn } from "@/utils/class-names";

function Popover({ ...props }: React.ComponentProps<typeof PopoverRadix.Root>) {
  return <PopoverRadix.Root data-slot="popover" {...props} />;
}

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

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

function PopoverPortal({
  ...props
}: React.ComponentProps<typeof PopoverRadix.Portal>) {
  return <PopoverRadix.Portal data-slot="popover-portal" {...props} />;
}

function PopoverContent({
  className,
  ...props
}: React.ComponentProps<typeof PopoverRadix.Content>) {
  return (
    <PopoverRadix.Portal>
      <PopoverRadix.Content
        data-slot="popover-content"
        collisionPadding={8}
        className={cn(
          className,
          "bg-gray-1 border border-gray-a6 rounded-sm relative shadow-md"
        )}
        {...props}
      />
    </PopoverRadix.Portal>
  );
}

function PopoverArrow({
  className,
  ...props
}: React.ComponentProps<typeof PopoverRadix.Arrow>) {
  return (
    <PopoverRadix.Arrow
      data-slot="popover-arrow"
      className={cn(className, "fill-gray-1")}
      {...props}
    />
  );
}

function PopoverClose({
  className,
  ...props
}: React.ComponentProps<typeof PopoverRadix.Close>) {
  return (
    <PopoverRadix.Close
      data-slot="popover-close"
      className={cn(
        className,
        "absolute right-0.5 top-0.5 inline-flex items-center justify-center rounded-sm p-0.5 hover:bg-accent-3 cursor-pointer"
      )}
      {...props}
    >
      <Cross2Icon className="size-1" />
      <span className="sr-only">Close</span>
    </PopoverRadix.Close>
  );
}

export {
  Popover,
  PopoverTrigger,
  PopoverAnchor,
  PopoverPortal,
  PopoverContent,
  PopoverArrow,
  PopoverClose,
};

Usage

WithCloseButton

export const WithCloseButton = () => (
  <Popover>
    <PopoverTrigger asChild>
      <Button variant="menu">Open Popover</Button>
    </PopoverTrigger>
    <PopoverContent className="w-30 p-1">
      <PopoverClose />
      <div className="space-y-1">
        <h4 className="font-medium leading-none">Notifications</h4>
        <p className="text-sm text-gray-11">
          You have 3 unread messages. Click the close button to dismiss.
        </p>
      </div>
    </PopoverContent>
  </Popover>
)

WithArrow

export const WithArrow = () => (
  <Popover>
    <PopoverTrigger asChild>
      <Button variant="menu">Open Popover</Button>
    </PopoverTrigger>
    <PopoverContent className="w-30 p-1">
      <PopoverArrow />
      <div className="space-y-1">
        <h4 className="font-medium leading-none">Info</h4>
        <p className="text-sm text-gray-11">
          This popover includes an arrow pointing to the trigger.
        </p>
      </div>
    </PopoverContent>
  </Popover>
)

Controlled

export const Controlled = () => {
  const [open, setOpen] = useState(false);

  return (
    <div className="space-y-1">
      <div className="flex items-center gap-0.5">
        <span className="text-sm text-gray-11">Status:</span>
        <span className="text-sm font-medium">{open ? "Open" : "Closed"}</span>
      </div>
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button variant="menu">Toggle Popover</Button>
        </PopoverTrigger>
        <PopoverContent className="w-30 p-1">
          <div className="space-y-1">
            <h4 className="font-medium leading-none">Controlled Popover</h4>
            <p className="text-sm text-gray-11">
              This popover's open state is controlled by React state.
            </p>
            <Button
              onClick={() => setOpen(false)}
              variant="link"
              className="w-full mt-0.5"
            >
              Close
            </Button>
          </div>
        </PopoverContent>
      </Popover>
    </div>
  );
};

WithAnchor

export const WithAnchor = () => (
  <div className="space-y-1">
    <p className="text-sm text-gray-11">
      The popover can be anchored to a different element than the trigger.
    </p>
    <Popover>
      <PopoverAnchor asChild>
        <div className="inline-block p-1 bg-accent-3 rounded-sm">
          Anchor Element
        </div>
      </PopoverAnchor>
      <PopoverTrigger asChild>
        <Button variant="menu" className="ml-1">
          Open (Anchored)
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-30 p-1">
        <div className="space-y-1">
          <h4 className="font-medium leading-none">Anchored Popover</h4>
          <p className="text-sm text-gray-11">
            This popover is positioned relative to the anchor element, not the
            trigger.
          </p>
        </div>
      </PopoverContent>
    </Popover>
  </div>
)

Positioning

export const Positioning = () => (
  <div className="grid grid-cols-2 gap-1">
    {(["top", "right", "bottom", "left"] as const).map((side) => (
      <Popover key={side}>
        <PopoverTrigger asChild>
          <Button variant="menu" className="capitalize">
            {side}
          </Button>
        </PopoverTrigger>
        <PopoverContent side={side} className="w-15 p-1">
          <p className="text-sm">
            Popover positioned on the <strong>{side}</strong> side.
          </p>
        </PopoverContent>
      </Popover>
    ))}
  </div>
)