eo-n/ui

Popover

Shows contextual information in an overlay anchored to another element.

import { Button } from "@/components/ui/button";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
 
export function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="outline">Open</Button>} />
      <PopoverContent className="w-80">
        <PopoverHeader>
          <PopoverTitle>Event Details</PopoverTitle>
          <PopoverDescription>
            Join us for an exclusive workshop on modern web development. Click
            below to register or learn more.
          </PopoverDescription>
        </PopoverHeader>
      </PopoverContent>
    </Popover>
  );
}

Installation

npx shadcn@latest add @eo-n/popover

Usage

Import all parts and piece them together.

import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
<Popover>
  <PopoverTrigger>Open</PopoverTrigger>
  <PopoverContent>
    <PopoverTitle>Popover Title</PopoverTitle>
    <PopoverDescription>
      This is some content inside the popover.
    </PopoverDescription>
  </PopoverContent>
</Popover>

Examples

Open On Hover

import { Dot } from "lucide-react";
 
import { Button } from "@/components/ui/button";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
 
export function PopoverHover() {
  return (
    <Popover>
      <PopoverTrigger
        openOnHover
        render={<Button variant="outline">Event Details</Button>}
      />
      <PopoverContent className="w-[325px] space-y-3">
        <PopoverHeader>
          <PopoverTitle>React Summit 2025</PopoverTitle>
          <PopoverDescription>
            Join us for the biggest React conference of the year featuring
            workshops, keynotes, and networking opportunities.
          </PopoverDescription>
        </PopoverHeader>
        <div className="mb-3 space-y-2">
          <div className="flex items-start">
            <Dot className="mt-0.5 h-4 w-4 flex-shrink-0" />
            <p className="ml-2 text-sm">20+ speakers from top tech companies</p>
          </div>
          <div className="flex items-start">
            <Dot className="mt-0.5 h-4 w-4 flex-shrink-0" />
            <p className="ml-2 text-sm">Hands-on workshops on React 20</p>
          </div>
          <div className="flex items-start">
            <Dot className="mt-0.5 h-4 w-4 flex-shrink-0" />
            <p className="ml-2 text-sm">
              Early bird pricing available until Feb 15
            </p>
          </div>
        </div>
        <Button className="w-full">View Full Schedule</Button>
      </PopoverContent>
    </Popover>
  );
}

Animated Popovers

import * as React from "react";
import { Bell, Settings2 } from "lucide-react";
 
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Popover,
  PopoverContent,
  PopoverCreateHandle,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
 
const demoPopover = PopoverCreateHandle<React.ComponentType>();
 
function NotificationsContent() {
  return (
    <div className="w-60">
      <PopoverHeader>
        <PopoverTitle>Notifications</PopoverTitle>
        <PopoverDescription>You have no new notifications.</PopoverDescription>
      </PopoverHeader>
      <div className="text-muted-foreground mt-4 grid place-items-center rounded-md border border-dashed py-12 text-sm">
        No new notifications
      </div>
    </div>
  );
}
 
function SettingsContent() {
  return (
    <div className="w-80">
      <PopoverHeader>
        <PopoverTitle>Settings</PopoverTitle>
        <PopoverDescription>Manage your account settings.</PopoverDescription>
      </PopoverHeader>
      <div className="mt-4 grid gap-4">
        <div className="grid gap-2">
          <Label htmlFor="username">Username</Label>
          <Input
            id="username"
            autoFocus
            defaultValue="@aeonzz"
            className="col-span-2 h-8"
          />
        </div>
        <div className="grid gap-2">
          <Label htmlFor="domain">Domain</Label>
          <Input
            id="domain"
            defaultValue="aeonz.dev"
            className="col-span-2 h-8"
          />
        </div>
      </div>
    </div>
  );
}
 
export function PopoverAnimated() {
  return (
    <div className="flex gap-2">
      <PopoverTrigger
        handle={demoPopover}
        payload={NotificationsContent}
        render={(props, state) => (
          <Button variant="outline" size="icon" active={state.open} {...props}>
            <Bell />
            <span className="sr-only">Notifications</span>
          </Button>
        )}
      />
      <PopoverTrigger
        handle={demoPopover}
        payload={SettingsContent}
        render={(props, state) => (
          <Button variant="outline" active={state.open} size="icon" {...props}>
            <Settings2 />
            <span className="sr-only">Settings</span>
          </Button>
        )}
      />
      <Popover handle={demoPopover}>
        {({ payload: Payload }) => (
          <PopoverContent>
            {Payload !== undefined && <Payload />}
          </PopoverContent>
        )}
      </Popover>
    </div>
  );
}