Skip to content

Popover

Rich content beside a trigger, HeroUI's on Kobalte's.

Solidtype_heroui.Popover is Kobalte’s Popover, with the classes HeroUI’s own popoverVariants gives and HeroUI’s arrow. A Button inside it is its trigger, as in HeroUI; anything else is wrapped in Popover.Trigger.

popover_usage.mlx
module Button = Solidtype_heroui.Button
module Popover = Solidtype_heroui.Popover
let make () =
<div class_="flex items-center gap-4">
<Popover>
<Button>"Click me"</Button>
<Popover.Content class_="max-w-64">
<Popover.Dialog>
<Popover.Heading>"Popover Title"</Popover.Heading>
<p class_="mt-2 text-sm text-muted">
"This is the popover content. You can put any content here."
</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
</div>
<Popover>
<Popover.Trigger>...</Popover.Trigger>
<Popover.Content>
<Popover.Arrow />
<Popover.Dialog>
<Popover.Heading>...</Popover.Heading>
...
</Popover.Dialog>
</Popover.Content>
</Popover>
popover_with_arrow.mlx
module Button = Solidtype_heroui.Button
module Popover = Solidtype_heroui.Popover
let make () =
<div class_="flex items-center gap-4">
<Popover>
<Button variant=`secondary>"With Arrow"</Button>
<Popover.Content class_="max-w-64">
<Popover.Dialog>
<Popover.Arrow />
<Popover.Heading>"Popover with Arrow"</Popover.Heading>
<p class_="mt-2 text-sm text-muted">
"The arrow shows which element triggered the popover."
</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
<Popover>
<Button is_icon_only=true aria_label="More options" variant=`tertiary>
<Gravity.Ellipsis />
</Button>
<Popover.Content class_="max-w-64" offset=10.>
<Popover.Dialog>
<Popover.Arrow />
<Popover.Heading>"Popover with Arrow"</Popover.Heading>
<p class_="mt-2 text-sm text-muted">
"The arrow shows which element triggered the popover."
</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
</div>
popover_interactive.mlx
module S = Solidtype.Solid
module Jsx = Solidtype.Jsx
module Avatar = Solidtype_heroui.Avatar
module Button = Solidtype_heroui.Button
module Popover = Solidtype_heroui.Popover
let make () =
let following, set_following = S.create_signal false in
<div class_="flex items-center gap-6">
<Popover>
<Popover.Trigger aria_label="User profile">
<div class_="flex items-center gap-2">
<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 class_="flex flex-col">
<p class_="text-sm font-medium">"Sarah Johnson"</p>
<p class_="text-xs text-muted">"@sarahj"</p>
</div>
</div>
</Popover.Trigger>
<Popover.Content class_="w-[320px]">
<Popover.Dialog>
<Popover.Heading>
<div class_="flex items-center justify-between">
<div class_="flex items-center gap-3">
<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 class_="font-semibold">"Sarah Johnson"</p>
<p class_="text-sm text-muted">"@sarahj"</p>
</div>
</div>
<Button
class_="rounded-full"
size=`sm
variant=(fun () ->
if S.get following then `tertiary else `primary)
on_click=(fun _ -> S.update set_following not)>
(fun () ->
Jsx.string (if S.get following then "Following" else "Follow"))
</Button>
</div>
</Popover.Heading>
<p class_="mt-3 text-sm text-muted">
"Product designer and creative director. Building beautiful \
experiences that matter."
</p>
<div class_="mt-3 flex gap-4">
<div>
<span class_="font-semibold">"892"</span>
<span class_="ms-1 text-sm text-muted">"Following"</span>
</div>
<div>
<span class_="font-semibold">"12.5K"</span>
<span class_="ms-1 text-sm text-muted">"Followers"</span>
</div>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover>
</div>
Click buttons
popover_placement.mlx
module Button = Solidtype_heroui.Button
module Popover = Solidtype_heroui.Popover
let make () =
<div class_="grid grid-cols-3 gap-4">
<div />
<Popover>
<Button class_="w-full" variant=`tertiary>"Top"</Button>
<Popover.Content placement=`top>
<Popover.Dialog>
<Popover.Arrow /> <p class_="text-sm">"Top placement"</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
<div />
<Popover>
<Button class_="w-full" variant=`tertiary>"Left"</Button>
<Popover.Content placement=`left>
<Popover.Dialog>
<Popover.Arrow /> <p class_="text-sm">"Left placement"</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
<div class_="flex items-center justify-center">
<span class_="text-sm text-muted">"Click buttons"</span>
</div>
<Popover>
<Button class_="w-full" variant=`tertiary>"Right"</Button>
<Popover.Content placement=`right>
<Popover.Dialog>
<Popover.Arrow /> <p class_="text-sm">"Right placement"</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
<div />
<Popover>
<Button class_="w-full" variant=`tertiary>"Bottom"</Button>
<Popover.Content placement=`bottom>
<Popover.Dialog>
<Popover.Arrow /> <p class_="text-sm">"Bottom placement"</p>
</Popover.Dialog>
</Popover.Content>
</Popover>
<div />
</div>

HeroUI’s Render Function example gives the content render, React’s way to swap the element it renders, which a Solid component has no need of.

class_ is added after HeroUI’s classes.

popover_tailwind.mlx
module Button = Solidtype_heroui.Button
module Popover = Solidtype_heroui.Popover
let make () =
<Popover>
<Button variant=`secondary>"Details"</Button>
<Popover.Content
class_="max-w-56 overflow-hidden rounded-xl border border-border/80 \
bg-surface/90 p-0 shadow-xl ring-1 ring-black/5 backdrop-blur-xl \
dark:border-border/90 dark:bg-surface/85 dark:ring-white/10">
<Popover.Dialog class_="relative p-4">
<div
aria_hidden=true
class_="pointer-events-none absolute inset-x-0 top-0 h-12 \
bg-linear-to-b from-neutral-500/6 to-transparent \
dark:from-neutral-400/8"
/>
<Popover.Heading
class_="relative font-medium text-neutral-800 dark:text-neutral-100">
"Keyboard shortcuts"
</Popover.Heading>
<dl class_="relative mt-3 space-y-2 text-sm">
<div class_="flex justify-between gap-4">
<dt class_="text-muted">"Save"</dt>
<dd class_="font-mono text-neutral-700 dark:text-neutral-300">
{js|⌘ S|js}
</dd>
</div>
<div class_="flex justify-between gap-4">
<dt class_="text-muted">"Search"</dt>
<dd class_="font-mono text-neutral-700 dark:text-neutral-300">
{js|⌘ K|js}
</dd>
</div>
</dl>
</Popover.Dialog>
</Popover.Content>
</Popover>

HeroUI’s classes are global, so a stylesheet restyles every one, as with HeroUI in React:

@layer components {
.popover {
@apply rounded-xl shadow-2xl;
}
.popover__dialog {
@apply p-4;
}
.popover__heading {
@apply text-lg font-bold;
}
}
  • .popover, .popover__dialog, .popover__heading, .popover__trigger
  • Entering: [data-entering="true"], while it appears
  • Exiting: [data-exiting="true"], while it disappears
  • Placement: [data-placement="bottom"], and the other sides
  • Focus: :focus-visible or [data-focus-visible="true"] on a trigger

Kobalte’s popover opens, closes, places itself, keeps the focus inside it and hides the page outside from a reader while it is open, as React Aria’s does. The port gives the focus to its dialog as it opens, as React Aria’s does, where Kobalte’s gives it to the first button inside, and back to its trigger as it closes. Its trigger shows pressed while it is open, as HeroUI’s does, and a button inside it is a button, not a second trigger. Either side of it is a dismiss button only a reader finds, as React Aria puts one; here it is a type="button", so a popover inside a form does not submit it.

Kobalte’s content is the dialog, named by its heading, where React Aria’s is the section inside it; a reader is told the same. Its arrow is drawn as Tooltip’s is: Kobalte’s, hidden, places HeroUI’s.

Each prop beside the name HeroUI’s React component gives it.

React solidtype Type Default Description
isOpen is_open bool Whether it is open, controlled
defaultOpen default_open bool false Whether it starts open
onOpenChange on_open_change bool -> unit Told as it opens or closes
children children elements Its trigger and its content
React solidtype Type Default Description
placement placement `top | `bottom | `left | `right `bottom Its side of the trigger
offset offset float 8 Its distance from the trigger
shouldFlip should_flip bool true Whether it moves to the other side where there is no room
className class_ string Added after HeroUI’s classes
children children elements What it shows
React solidtype Type Description
className class_ string Added after HeroUI’s classes
children children elements What it holds
React solidtype Type Description
aria-label aria_label string Its name, where its children give none
className class_ string Added after HeroUI’s classes
children children elements What it wraps
React solidtype Type Description
className class_ string Its class
children children elements An arrow of its own, in place of HeroUI’s

Original HeroUI documentation: Popover.

The same HeroUI Popover twice, under the same props: in solidtype on the left, in React on the right. Below them, every attribute of each trigger: a marked row is one they disagree on, and a dimmed row is a framework’s own, which no style reads.

solidtype — OCaml, on Solid and Kobalte
React — TypeScript, on React Aria
attributesolidtypeReact