Hover Card
A popup that displays rich content when a trigger is hovered.
Loading...
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="link" {{ ...hover_card_trigger_attrs }}>Hover Here</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="w-64 flex flex-col gap-0.5">
<div class="font-semibold">@symfony</div>
<div>The PHP framework for web applications — created by @fabpot.</div>
<div class="mt-1 text-xs text-muted-foreground">Joined October 2010</div>
</twig:HoverCard:Content>
</twig:HoverCard>
Installation
bin/console ux:install hover-card --kit shadcn
That's it!
Install the following Composer dependencies:
composer require tales-from-a-dev/twig-tailwind-extra:^1.0.0
Copy the following file(s) into your Symfony app:
assets/controllers/hover_card_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static values = {
openDelay: { type: Number, default: 0 },
closeDelay: { type: Number, default: 0 },
};
connect() {
this.openTimeout = null;
this.closeTimeout = null;
this.element.dataset.state = 'closed';
}
disconnect() {
this.#clearTimeouts();
}
show() {
this.#clearTimeouts();
this.openTimeout = setTimeout(() => {
this.element.dataset.state = 'open';
this.openTimeout = null;
}, this.openDelayValue);
}
hide() {
this.#clearTimeouts();
this.closeTimeout = setTimeout(() => {
this.element.dataset.state = 'closed';
this.closeTimeout = null;
}, this.closeDelayValue);
}
#clearTimeouts() {
if (this.openTimeout) {
clearTimeout(this.openTimeout);
this.openTimeout = null;
}
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
}
}
}
templates/components/HoverCard.html.twig
{# @prop openDelay number Delay in milliseconds before showing the content. Defaults to `0` #}
{# @prop closeDelay number Delay in milliseconds before hiding the content. Defaults to `0` #}
{# @block content The hover card structure, typically a `HoverCard:Trigger` and `HoverCard:Content` #}
{%- props openDelay = 0, closeDelay = 0 -%}
<span
class="{{ ('relative inline-block ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({
'data-slot': 'hover-card',
'data-controller': 'hover-card',
'data-hover-card-open-delay-value': openDelay,
'data-hover-card-close-delay-value': closeDelay,
'data-action': 'mouseenter->hover-card#show mouseleave->hover-card#hide',
}) }}
>
{%- block content %}{% endblock -%}
</span>
templates/components/HoverCard/Content.html.twig
{# @block content The content revealed on hover #}
<span
role="tooltip"
class="{{ ('invisible opacity-0 in-data-[state=open]:visible in-data-[state=open]:opacity-100 transition-opacity absolute left-1/2 top-full z-50 mt-2 w-64 -translate-x-1/2 rounded-md border bg-popover p-4 text-sm text-popover-foreground shadow-md outline-none ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({
'data-slot': 'hover-card-content',
}) }}
>
{%- block content %}{% endblock -%}
</span>
templates/components/HoverCard/Trigger.html.twig
{# @block content The element that reveals the hover card on hover or focus #}
{%- set hover_card_trigger_attrs = {
'data-slot': 'hover-card-trigger',
tabindex: 0,
'data-action': 'focus->hover-card#show blur->hover-card#hide'|html_attr_type('sst'),
} -%}
{%- block content %}{% endblock -%}
Happy coding!
Usage
<twig:HoverCard>
<twig:HoverCard:Trigger>
<a href="#" class="underline" {{ ...hover_card_trigger_attrs }}>@symfony</a>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content>
The Symfony PHP framework — official organization on GitHub.
</twig:HoverCard:Content>
</twig:HoverCard>
Examples
Sides
Loading...
<div class="flex flex-wrap justify-center gap-12 py-12">
{# Left #}
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>Left</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-auto right-full translate-x-0 -translate-y-1/2 mt-0 mr-2">
<div class="flex flex-col gap-1">
<h4 class="font-medium">Hover Card</h4>
<p>This hover card appears on the left side of the trigger.</p>
</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{# Top #}
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>Top</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-auto bottom-full mt-0 mb-2">
<div class="flex flex-col gap-1">
<h4 class="font-medium">Hover Card</h4>
<p>This hover card appears on the top side of the trigger.</p>
</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{# Bottom (default) #}
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>Bottom</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content>
<div class="flex flex-col gap-1">
<h4 class="font-medium">Hover Card</h4>
<p>This hover card appears on the bottom side of the trigger.</p>
</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{# Right #}
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>Right</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-full translate-x-0 -translate-y-1/2 mt-0 ml-2">
<div class="flex flex-col gap-1">
<h4 class="font-medium">Hover Card</h4>
<p>This hover card appears on the right side of the trigger.</p>
</div>
</twig:HoverCard:Content>
</twig:HoverCard>
</div>
RTL
Loading...
<div class="flex flex-col items-center gap-24 py-12">
{# Arabic #}
<div class="flex flex-wrap justify-center gap-2" dir="rtl">
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>يسار</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-auto right-full translate-x-0 -translate-y-1/2 mt-0 mr-2 w-64 flex flex-col gap-1">
<div class="font-semibold">سماعات لاسلكية</div>
<div class="text-sm text-muted-foreground">٩٩.٩٩ $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>أعلى</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-auto bottom-full mt-0 mb-2 w-64 flex flex-col gap-1">
<div class="font-semibold">سماعات لاسلكية</div>
<div class="text-sm text-muted-foreground">٩٩.٩٩ $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>أسفل</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="w-64 flex flex-col gap-1">
<div class="font-semibold">سماعات لاسلكية</div>
<div class="text-sm text-muted-foreground">٩٩.٩٩ $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>يمين</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-full translate-x-0 -translate-y-1/2 mt-0 ml-2 w-64 flex flex-col gap-1">
<div class="font-semibold">سماعات لاسلكية</div>
<div class="text-sm text-muted-foreground">٩٩.٩٩ $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
</div>
{# Hebrew #}
<div class="flex flex-wrap justify-center gap-2" dir="rtl">
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>שמאל</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-auto right-full translate-x-0 -translate-y-1/2 mt-0 mr-2 w-64 flex flex-col gap-1">
<div class="font-semibold">אוזניות אלחוטיות</div>
<div class="text-sm text-muted-foreground">99.99 $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>למעלה</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-auto bottom-full mt-0 mb-2 w-64 flex flex-col gap-1">
<div class="font-semibold">אוזניות אלחוטיות</div>
<div class="text-sm text-muted-foreground">99.99 $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>למטה</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="w-64 flex flex-col gap-1">
<div class="font-semibold">אוזניות אלחוטיות</div>
<div class="text-sm text-muted-foreground">99.99 $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
<twig:HoverCard>
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>ימין</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="top-1/2 left-full translate-x-0 -translate-y-1/2 mt-0 ml-2 w-64 flex flex-col gap-1">
<div class="font-semibold">אוזניות אלחוטיות</div>
<div class="text-sm text-muted-foreground">99.99 $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
</div>
</div>
API Reference
HoverCard
| Prop | Type | Description |
|---|---|---|
openDelay |
number |
Delay in milliseconds before showing the content. Defaults to 0 |
closeDelay |
number |
Delay in milliseconds before hiding the content. Defaults to 0 |
| Block | Description |
|---|---|
content |
The hover card structure, typically a HoverCard:Trigger and HoverCard:Content |
HoverCard:Content
| Block | Description |
|---|---|
content |
The content revealed on hover |
HoverCard:Trigger
| Block | Description |
|---|---|
content |
The element that reveals the hover card on hover or focus |