Popover
Displays rich content in a portal triggered by a button or any custom element
Usage
import { Popover } from '@lenso/ui';"use client";
// Adapted from HeroUI v3.2.6 popover-basic (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, Popover } from "@lenso/ui";import { styles } from "./styles";
export function PopoverBasic() { return ( <div {...stylex.props(styles.row)}> <Popover> <Popover.Trigger render={<Button />}>Click me</Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={8}> <Popover.Popup xstyle={styles.popup}> <Popover.Title>Popover Title</Popover.Title> <Popover.Description xstyle={styles.description}> This is the popover content. You can put any content here. </Popover.Description> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
import { Popover } from '@lenso/ui';
export default () => ( <Popover> <Popover.Trigger/> <Popover.Content> <Popover.Arrow /> <Popover.Dialog> <Popover.Heading/> {/* content goes here */} </Popover.Dialog> </Popover.Content> </Popover>)Examples
With Arrow
"use client";
// Adapted from HeroUI v3.2.6 popover-with-arrow (Apache-2.0).import { Ellipsis } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, Popover } from "@lenso/ui";import { styles } from "./styles";
export function PopoverWithArrow() { return ( <div {...stylex.props(styles.row)}> <Popover> <Popover.Trigger render={<Button variant="secondary" />}>With Arrow</Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={8}> <Popover.Popup xstyle={styles.popup}> <Popover.Arrow /> <Popover.Title>Popover with Arrow</Popover.Title> <Popover.Description xstyle={styles.description}> The arrow shows which element triggered the popover. </Popover.Description> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> <Popover> <Popover.Trigger render={<Button isIconOnly aria-label="More options" variant="tertiary" />} > <Button.Icon> <Ellipsis /> </Button.Icon> </Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={10}> <Popover.Popup xstyle={styles.popup}> <Popover.Arrow /> <Popover.Title>Popover with Arrow</Popover.Title> <Popover.Description xstyle={styles.description}> The arrow shows which element triggered the popover. </Popover.Description> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Interactive Content
"use client";
// Adapted from HeroUI v3.2.6 popover-interactive (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Avatar, Button, Popover } from "@lenso/ui";import { useState } from "react";import { styles } from "./styles";
export function PopoverInteractive() { const [isFollowing, setIsFollowing] = useState(false); return ( <div {...stylex.props(styles.profileRow)}> <Popover> <Popover.Trigger aria-label="User profile"> <div {...stylex.props(styles.identity)}> <Avatar size="sm"> <Avatar.Image alt="Sarah Johnson" src="https://img.heroui.chat/image/avatar?w=400&h=400&u=1" /> <Avatar.Fallback>SJ</Avatar.Fallback> </Avatar> <div {...stylex.props(styles.identityText)}> <p {...stylex.props(styles.name)}>Sarah Johnson</p> <p {...stylex.props(styles.handle)}>@sarahj</p> </div> </div> </Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={8}> <Popover.Popup xstyle={styles.profilePopup}> <Popover.Title render={<div />}> <div {...stylex.props(styles.profileHeading)}> <div {...stylex.props(styles.identityLarge)}> <Avatar size="md"> <Avatar.Image alt="Sarah Johnson" src="https://img.heroui.chat/image/avatar?w=400&h=400&u=1" /> <Avatar.Fallback>SJ</Avatar.Fallback> </Avatar> <div> <p {...stylex.props(styles.strong)}>Sarah Johnson</p> <p {...stylex.props(styles.muted)}>@sarahj</p> </div> </div> <Button xstyle={styles.follow} size="sm" variant={isFollowing ? "tertiary" : "primary"} onClick={() => setIsFollowing(!isFollowing)} > {isFollowing ? "Following" : "Follow"} </Button> </div> </Popover.Title> <Popover.Description xstyle={styles.bio}> Product designer and creative director. Building beautiful experiences that matter. </Popover.Description> <div {...stylex.props(styles.statistics)}> <div> <span {...stylex.props(styles.strong)}>892</span> <span {...stylex.props(styles.statisticLabel)}>Following</span> </div> <div> <span {...stylex.props(styles.strong)}>12.5K</span> <span {...stylex.props(styles.statisticLabel)}>Followers</span> </div> </div> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Placement
"use client";
// Adapted from HeroUI v3.2.6 popover-placement (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, Popover } from "@lenso/ui";import { styles } from "./styles";
export function PopoverPlacement() { return ( <div {...stylex.props(styles.grid)}> <div /> <Popover> <Popover.Trigger render={<Button xstyle={styles.fullWidth} variant="tertiary" />}> Top </Popover.Trigger> <Popover.Portal> <Popover.Positioner side="top" sideOffset={8}> <Popover.Popup> <Popover.Arrow /> <p {...stylex.props(styles.small)}>Top placement</p> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> <div /> <Popover> <Popover.Trigger render={<Button xstyle={styles.fullWidth} variant="tertiary" />}> Left </Popover.Trigger> <Popover.Portal> <Popover.Positioner side="left" sideOffset={8}> <Popover.Popup> <Popover.Arrow /> <p {...stylex.props(styles.small)}>Left placement</p> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> <div {...stylex.props(styles.center)}> <span {...stylex.props(styles.muted)}>Click buttons</span> </div> <Popover> <Popover.Trigger render={<Button xstyle={styles.fullWidth} variant="tertiary" />}> Right </Popover.Trigger> <Popover.Portal> <Popover.Positioner side="right" sideOffset={8}> <Popover.Popup> <Popover.Arrow /> <p {...stylex.props(styles.small)}>Right placement</p> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> <div /> <Popover> <Popover.Trigger render={<Button xstyle={styles.fullWidth} variant="tertiary" />}> Bottom </Popover.Trigger> <Popover.Portal> <Popover.Positioner side="bottom" sideOffset={8}> <Popover.Popup> <Popover.Arrow /> <p {...stylex.props(styles.small)}>Bottom placement</p> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> <div /> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Render Function
"use client";
// Adapted from HeroUI v3.2.6 popover-render-function (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, Popover } from "@lenso/ui";import { styles } from "./styles";
export function RenderFunction() { return ( <div {...stylex.props(styles.row)}> <Popover> <Popover.Trigger render={<Button />}>Click me</Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={8}> <Popover.Popup xstyle={styles.popup} render={(props) => <div {...props} data-custom="foo" />} > <Popover.Title>Popover Title</Popover.Title> <Popover.Description xstyle={styles.description}> This is the popover content. You can put any content here. </Popover.Description> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Customization
Tailwind CSS
"use client";
// Adapted from HeroUI v3.2.6 popover-custom-styles (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, Popover } from "@lenso/ui";import { styles } from "./styles";
export function CustomStyles() { return ( <Popover> <Popover.Trigger render={<Button variant="secondary" />}>Details</Popover.Trigger> <Popover.Portal> <Popover.Positioner sideOffset={8}> <Popover.Popup xstyle={styles.customPopup}> <div {...stylex.props(styles.customDialog)}> <div aria-hidden="true" {...stylex.props(styles.highlight)} /> <Popover.Title xstyle={styles.customHeading}>Keyboard shortcuts</Popover.Title> <dl {...stylex.props(styles.shortcuts)}> <div {...stylex.props(styles.shortcut)}> <dt {...stylex.props(styles.muted)}>Save</dt> <dd {...stylex.props(styles.key)}>⌘ S</dd> </div> <div {...stylex.props(styles.shortcut)}> <dt {...stylex.props(styles.muted)}>Search</dt> <dd {...stylex.props(styles.key)}>⌘ K</dd> </div> </dl> </div> </Popover.Popup> </Popover.Positioner> </Popover.Portal> </Popover> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
To customize the Popover component classes, you can use the @layer components directive.
Learn more.
@layer components { .popover { @apply rounded-xl shadow-2xl; }
.popover__dialog { @apply p-4; }
.popover__heading { @apply text-lg font-bold; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Popover component uses these CSS classes (View source styles):
Base Classes [!toc]
.popover- Base popover container styles.popover__dialog- Dialog content wrapper.popover__heading- Heading text styles.popover__trigger- Trigger element styles
Interactive States
The component supports animation states:
- Entering:
[data-entering]- Applied during popover appearance - Exiting:
[data-exiting]- Applied during popover disappearance - Placement:
[data-placement="*"]- Applied based on popover position - Focus:
:focus-visibleor[data-focus-visible="true"]
API Reference
Popover
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Trigger and content elements |
isOpen | boolean | - | Controls popover visibility (controlled) |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (isOpen: boolean) => void | - | Called when open state changes |
Popover.Content
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Content to display in the popover |
placement | "top" | "bottom" | "left" | "right" (and variants) | "bottom" | Placement of the popover |
offset | number | 8 | Distance from the trigger element |
shouldFlip | boolean | true | Whether popover can change orientation to fit |
className | string | - | Additional CSS classes |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, PopoverRenderProps> | - | Overrides the default DOM element with a custom render function. |
Popover.Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Dialog content |
className | string | - | Additional CSS classes |
Popover.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Element that triggers the popover |
className | string | - | Additional CSS classes |
Popover.Arrow
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Custom arrow element |
className | string | - | Additional CSS classes |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, OverlayArrowRenderProps> | - | Overrides the default DOM element with a custom render function. |