For sighted users to preview content available behind a link.
<HoverCard.root
id="hover-card-primitive"
open_delay={100}
close_delay={100}
>
<HoverCard.trigger
id="hover-card-trigger"
content_id="hover-card-content"
>
<a
class="DemoImageTrigger"
href="https://github.com/"
target="_blank"
rel="noreferrer noopener"
>
<div
class="DemoImage normal"
style="display: flex; align-items: center; justify-content: center; background: var(--gray-3); color: var(--gray-11); font-weight: 600; font-size: 14px;"
aria-hidden="true"
>
E
</div>
</a>
</HoverCard.trigger>
<HoverCard.portal id="hover-card-portal">
<HoverCard.content
id="hover-card-content"
class="DemoHoverCardContent"
side_offset={5}
>
<div style="display: flex; flex-direction: column; gap: 7px;">
<div
class="DemoImage large"
style="display: flex; align-items: center; justify-content: center; background: var(--gray-3); color: var(--gray-11); font-weight: 600; font-size: 24px;"
aria-hidden="true"
>
E
</div>
<div style="display: flex; flex-direction: column; gap: 15px;">
<div>
<div class="DemoText bold">Essence</div>
<div class="DemoText faded">@essence_ui</div>
</div>
<div class="DemoText">
Unstyled, accessible UI components for Phoenix LiveView. Primitives, colors, and themes for building high-quality interfaces.
</div>
<div style="display: flex; gap: 15px;">
<div style="display: flex; gap: 5px;">
<div class="DemoText bold">0</div>
<div class="DemoText faded">Following</div>
</div>
<div style="display: flex; gap: 5px;">
<div class="DemoText bold">2,900</div>
<div class="DemoText faded">Followers</div>
</div>
</div>
</div>
</div>
<HoverCard.arrow class="DemoHoverCardArrow" />
</HoverCard.content>
</HoverCard.portal>
</HoverCard.root>
/* reset */
a {
all: unset;
}
.DemoHoverCardContent {
border-radius: 6px;
padding: 20px;
width: 300px;
background-color: white;
box-shadow:
hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.DemoHoverCardContent[data-side="top"] {
animation-name: slideDownAndFade;
}
.DemoHoverCardContent[data-side="right"] {
animation-name: slideLeftAndFade;
}
.DemoHoverCardContent[data-side="bottom"] {
animation-name: slideUpAndFade;
}
.DemoHoverCardContent[data-side="left"] {
animation-name: slideRightAndFade;
}
.DemoHoverCardArrow {
fill: white;
}
.DemoImageTrigger {
cursor: pointer;
border-radius: 100%;
display: inline-block;
}
.DemoImageTrigger:focus {
box-shadow: 0 0 0 2px white;
}
.DemoImage {
display: block;
border-radius: 100%;
}
.DemoImage.normal {
width: 45px;
height: 45px;
}
.DemoImage.large {
width: 60px;
height: 60px;
}
.DemoText {
margin: 0;
color: var(--mauve-12);
font-size: 15px;
line-height: 1.5;
}
.DemoText.faded {
color: var(--mauve-10);
}
.DemoText.bold {
font-weight: 500;
}
@keyframes slideUpAndFade {
0% {
opacity: 0;
transform: translateY(2px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideRightAndFade {
0% {
opacity: 0;
transform: translateX(-2px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideDownAndFade {
0% {
opacity: 0;
transform: translateY(-2px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeftAndFade {
0% {
opacity: 0;
transform: translateX(2px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
Anatomy
Import all parts and piece them together.
<HoverCard.root>
<HoverCard.trigger />
<HoverCard.portal>
<HoverCard.content>
<HoverCard.arrow />
</HoverCard.content>
</HoverCard.portal>
</HoverCard.root>
Anatomy
-
Root - Contains all the parts of a hover card.
-
Trigger - The link that opens the hover card when hovered. Set `content_id` to the content element's `id` for `aria-describedby`.
-
Portal - When used, portals the content part into the target (default `body`).
-
Content - The component that pops out when the hover card is open.
-
Arrow - An optional arrow element to render alongside the content. Must be rendered inside `HoverCard.content`.
API Reference
Root
Contains all the parts of a hover card.
Use open with on_open_change for controlled open state in LiveView:
<HoverCard.root id="profile-card" open={@open} on_open_change="hover_card_open_change">
…
</HoverCard.root>
def handle_event("hover_card_open_change", %{"open" => open}, socket) do
{:noreply, assign(socket, :open, open)}
end
| Prop | Type | Default | Description |
|---|---|---|---|
close_delay
|
integer
|
150
|
|
default_open
|
boolean
|
false
|
|
id
|
string
|
—
|
|
on_open_change
|
string
|
nil
|
|
open
|
boolean
|
false
|
|
open_delay
|
integer
|
200
|
close_delay
integer
Default
150
default_open
boolean
Default
false
id
string
Default
—
on_open_change
string
Default
nil
open
boolean
Default
false
open_delay
integer
Default
200
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-state]
|
open | closed
|
Reflects whether the hover card is open. |
Trigger
The link that opens the hover card when hovered.
| Prop | Type | Default | Description |
|---|---|---|---|
content_id
|
string
|
nil
|
|
id
|
string
|
nil
|
content_id
string
Default
nil
id
string
Default
nil
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-state]
|
open | closed
|
Reflects whether the hover card is open. |
Portal
When used, portals the content part into the target (default body).
| Prop | Type | Default | Description |
|---|---|---|---|
class
|
any
|
nil
|
|
container
|
string
|
"div"
|
|
id
|
string
|
—
|
|
target
|
string
|
"body"
|
class
any
Default
nil
container
string
Default
"div"
id
string
Default
—
target
string
Default
"body"
Content
The component that pops out when the hover card is open.
| Prop | Type | Default | Description |
|---|---|---|---|
align
|
string
|
"center"
|
|
id
|
string
|
nil
|
|
side
|
string
|
"bottom"
|
|
side_offset
|
integer
|
8
|
align
string
Default
"center"
id
string
Default
nil
side
string
Default
"bottom"
side_offset
integer
Default
8
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-state]
|
open | closed
|
Reflects whether the hover card is open. |
[data-side]
|
top | right | bottom | left
|
Preferred side relative to the trigger. |
[data-align]
|
start | center | end
|
Alignment along the side. |
Arrow
An optional arrow element to render alongside the hover card. This can be used to help visually link the trigger with the content. Must be rendered inside HoverCard.content.
| Prop | Type | Default | Description |
|---|---|---|---|
id
|
string
|
nil
|
id
string
Default
nil
Examples
Show instantly
Use open_delay and close_delay to control the time it takes for the hover card to open and close.
<HoverCard.root id="instant-hover-card" open_delay={0} close_delay={0}>
<HoverCard.trigger id="instant-hover-card-trigger" content_id="instant-hover-card-content">
…
</HoverCard.trigger>
<HoverCard.portal id="instant-hover-card-portal">
<HoverCard.content id="instant-hover-card-content" class="DemoHoverCardContent">
…
</HoverCard.content>
</HoverCard.portal>
</HoverCard.root>
Accessibility
The hover card is intended for sighted users only; the content will be inaccessible to keyboard users.
Keyboard Interactions
| Key | Description |
|---|---|
Tab
|
Opens/closes the hover card. |
Enter
|
Opens the hover card link. |