Github

Popover

Displays rich content in a portal, triggered by a button.

"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
	Popover,
	PopoverContent,
	PopoverTrigger,
} from "@/components/ui/popover";

export function PopoverDemo() {
	return (
		<Popover>
			<PopoverTrigger render={<Button variant="tertiary" />}>
				Open popover
			</PopoverTrigger>
			<PopoverContent className="w-80">
				<div className="grid gap-4">
					<div className="space-y-2">
						<h4 className="font-medium leading-none">Dimensions</h4>
						<p className="text-muted-foreground text-sm">
							Set the dimensions for the layer.
						</p>
					</div>
					<div className="grid gap-2">
						<div className="grid grid-cols-3 items-center gap-4">
							<Label htmlFor="popover-width">Width</Label>
							<Input
								variant="secondary"
								id="popover-width"
								defaultValue="100%"
								className="col-span-2 h-8"
							/>
						</div>
						<div className="grid grid-cols-3 items-center gap-4">
							<Label htmlFor="popover-max-width">Max. width</Label>
							<Input
								variant="secondary"
								id="popover-max-width"
								defaultValue="300px"
								className="col-span-2 h-8"
							/>
						</div>
						<div className="grid grid-cols-3 items-center gap-4">
							<Label htmlFor="popover-height">Height</Label>
							<Input
								variant="secondary"
								id="popover-height"
								defaultValue="25px"
								className="col-span-2 h-8"
							/>
						</div>
						<div className="grid grid-cols-3 items-center gap-4">
							<Label htmlFor="popover-max-height">Max. height</Label>
							<Input
								variant="secondary"
								id="popover-max-height"
								defaultValue="none"
								className="col-span-2 h-8"
							/>
						</div>
					</div>
				</div>
			</PopoverContent>
		</Popover>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/popover.json

Usage

import { Button } from "@/components/ui/button"
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover"
<Popover>
  <PopoverTrigger render={<Button variant="outline" />}>
    Open Popover
  </PopoverTrigger>
  <PopoverContent>
    <PopoverHeader>
      <PopoverTitle>Title</PopoverTitle>
      <PopoverDescription>Description text here.</PopoverDescription>
    </PopoverHeader>
  </PopoverContent>
</Popover>

Composition

Use the following composition to build a Popover:

Popover
├── PopoverTrigger
└── PopoverContent

Examples

Basic

A simple popover with a header, title, and description.

"use client";

import { Button } from "@/components/ui/button";
import {
	Popover,
	PopoverContent,
	PopoverDescription,
	PopoverHeader,
	PopoverTitle,
	PopoverTrigger,
} from "@/components/ui/popover";

export function PopoverBasic() {
	return (
		<Popover>
			<PopoverTrigger render={<Button variant="tertiary" className="w-fit" />}>
				Open Popover
			</PopoverTrigger>
			<PopoverContent>
				<PopoverHeader>
					<PopoverTitle>Dimensions</PopoverTitle>
					<PopoverDescription>
						Set the dimensions for the layer.
					</PopoverDescription>
				</PopoverHeader>
			</PopoverContent>
		</Popover>
	);
}

Align

Use the align prop on PopoverContent to control the horizontal alignment relative to the trigger.

"use client";

import { Button } from "@/components/ui/button";
import {
	Popover,
	PopoverContent,
	PopoverTrigger,
} from "@/components/ui/popover";

export function PopoverAlign() {
	return (
		<div className="flex gap-6">
			<Popover>
				<PopoverTrigger render={<Button variant="tertiary" size="sm" />}>
					Start
				</PopoverTrigger>
				<PopoverContent align="start" className="w-40">
					Aligned to start
				</PopoverContent>
			</Popover>
			<Popover>
				<PopoverTrigger render={<Button variant="tertiary" size="sm" />}>
					Center
				</PopoverTrigger>
				<PopoverContent align="center" className="w-40">
					Aligned to center
				</PopoverContent>
			</Popover>
			<Popover>
				<PopoverTrigger render={<Button variant="tertiary" size="sm" />}>
					End
				</PopoverTrigger>
				<PopoverContent align="end" className="w-40">
					Aligned to end
				</PopoverContent>
			</Popover>
		</div>
	);
}

With form

A popover with form fields inside.

"use client";

import { Button } from "@/components/ui/button";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
	Popover,
	PopoverContent,
	PopoverDescription,
	PopoverHeader,
	PopoverTitle,
	PopoverTrigger,
} from "@/components/ui/popover";

export function PopoverForm() {
	return (
		<Popover>
			<PopoverTrigger render={<Button variant="tertiary" />}>
				Open Popover
			</PopoverTrigger>
			<PopoverContent className="w-64">
				<PopoverHeader>
					<PopoverTitle>Dimensions</PopoverTitle>
					<PopoverDescription>
						Set the dimensions for the layer.
					</PopoverDescription>
				</PopoverHeader>
				<FieldGroup className="gap-4">
					<Field orientation="horizontal">
						<FieldLabel htmlFor="popover-form-width" className="w-1/2">
							Width
						</FieldLabel>
						<Input
							variant="secondary"
							id="popover-form-width"
							defaultValue="100%"
						/>
					</Field>
					<Field orientation="horizontal">
						<FieldLabel htmlFor="popover-form-height" className="w-1/2">
							Height
						</FieldLabel>
						<Input
							variant="secondary"
							id="popover-form-height"
							defaultValue="25px"
						/>
					</Field>
				</FieldGroup>
			</PopoverContent>
		</Popover>
	);
}

With arrow

Use the withArrow prop on PopoverContent to show an arrow pointing at the trigger.

"use client";

import { Button } from "@/components/ui/button";
import {
	Popover,
	PopoverContent,
	PopoverDescription,
	PopoverHeader,
	PopoverTitle,
	PopoverTrigger,
} from "@/components/ui/popover";

export function PopoverWithArrow() {
	return (
		<Popover>
			<PopoverTrigger render={<Button variant="tertiary" className="w-fit" />}>
				Open Popover
			</PopoverTrigger>
			<PopoverContent withArrow>
				<PopoverHeader>
					<PopoverTitle>With Arrow</PopoverTitle>
					<PopoverDescription>
						This popover has an arrow at its top
					</PopoverDescription>
				</PopoverHeader>
			</PopoverContent>
		</Popover>
	);
}

RTL

To enable RTL support in shadcn/ui, see the RTL configuration guide.

"use client";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import {
	Popover,
	PopoverContent,
	PopoverDescription,
	PopoverHeader,
	PopoverTitle,
	PopoverTrigger,
} from "@/components/ui/popover";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			title: "Dimensions",
			description: "Set the dimensions for the layer.",
			"inline-start": "Inline Start",
			left: "Left",
			top: "Top",
			bottom: "Bottom",
			right: "Right",
			"inline-end": "Inline End",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			title: "الأبعاد",
			description: "تعيين الأبعاد للطبقة.",
			"inline-start": "بداية السطر",
			left: "يسار",
			top: "أعلى",
			bottom: "أسفل",
			right: "يمين",
			"inline-end": "نهاية السطر",
		},
	},
	he: {
		dir: "rtl",
		values: {
			title: "מימדים",
			description: "הגדר את המימדים לשכבה.",
			"inline-start": "תחילת השורה",
			left: "שמאל",
			top: "למעלה",
			bottom: "למטה",
			right: "ימין",
			"inline-end": "סוף השורה",
		},
	},
};

const physicalSides = ["left", "top", "bottom", "right"] as const;
const logicalSides = ["inline-start", "inline-end"] as const;

export function PopoverRtl() {
	const { dir, language, t } = useTranslation(translations, "ar");

	return (
		<div lang={language} dir={dir} className="grid max-w-lg gap-4">
			<div className="flex flex-wrap justify-center gap-2">
				{physicalSides.map((side) => (
					<Popover key={side}>
						<PopoverTrigger render={<Button variant="tertiary" size="sm" />}>
							{t[side]}
						</PopoverTrigger>
						<PopoverContent side={side} dir={dir}>
							<PopoverHeader>
								<PopoverTitle>{t.title}</PopoverTitle>
								<PopoverDescription>{t.description}</PopoverDescription>
							</PopoverHeader>
						</PopoverContent>
					</Popover>
				))}
			</div>
			<div className="flex flex-wrap justify-center gap-2">
				{logicalSides.map((side) => (
					<Popover key={side}>
						<PopoverTrigger render={<Button variant="tertiary" size="sm" />}>
							{t[side]}
						</PopoverTrigger>
						<PopoverContent side={side} dir={dir}>
							<PopoverHeader>
								<PopoverTitle>{t.title}</PopoverTitle>
								<PopoverDescription>{t.description}</PopoverDescription>
							</PopoverHeader>
						</PopoverContent>
					</Popover>
				))}
			</div>
		</div>
	);
}

API Reference

The popover is built on Base UI Popover. PopoverContent sets default positioning and passes the remaining props to the popup.

PopoverContent

PropTypeDefault

See the Base UI Popover API reference for root, trigger, title, description, and other props.