Hover Card
For sighted users to preview content available behind a link.
Loading...
<twig:HoverCard openDelay="10" closeDelay="100">
<twig:HoverCard:Trigger>
<twig:Button variant="link" {{ ...hover_card_trigger_attrs }}>Hover Here</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="flex w-64 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
Note
Available since UX Toolkit 3.0.
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
{# @prop side 'bottom'|'top'|'left'|'right' The side where the content appears. Defaults to `bottom` #}
{# @block content The content revealed on hover #}
{%- props side = 'bottom' -%}
{%- set style = html_cva(
base: 'invisible opacity-0 in-data-[state=open]:visible in-data-[state=open]:opacity-100 transition-opacity duration-100 absolute z-50 w-64 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden ',
variants: {
side: {
bottom: 'left-1/2 top-full mt-2 -translate-x-1/2',
top: 'left-1/2 top-auto bottom-full mb-2 mt-0 -translate-x-1/2',
left: 'top-1/2 left-auto right-full -translate-y-1/2 translate-x-0 mt-0 mr-2',
right: 'top-1/2 left-full -translate-y-1/2 translate-x-0 mt-0 ml-2',
},
},
default_variant: {
side: 'bottom',
},
) -%}
<span
role="tooltip"
data-side="{{ side }}"
class="{{ style.apply({side: side}, 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
Basic
Loading...
<twig:HoverCard openDelay="10" closeDelay="100">
<twig:HoverCard:Trigger>
<twig:Button variant="link" {{ ...hover_card_trigger_attrs }}>Hover Here</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content class="flex w-64 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>
Sides
Loading...
<div class="flex flex-wrap justify-center gap-2">
{% for side in ['left', 'top', 'bottom', 'right'] %}
<twig:HoverCard openDelay="100" closeDelay="100">
<twig:HoverCard:Trigger>
<twig:Button variant="outline" class="capitalize" {{ ...hover_card_trigger_attrs }}>{{ side }}</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content side="{{ side }}">
<div class="flex flex-col gap-1">
<h4 class="font-medium">Hover Card</h4>
<p>This hover card appears on the {{ side }} side of the trigger.</p>
</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{% endfor %}
</div>
RTL
To enable RTL support, set the dir="rtl" attribute on the root element.
Loading...
<div class="flex flex-col items-center gap-24 py-12">
{# Arabic #}
<div class="flex flex-wrap justify-center gap-2" dir="rtl">
{% for side, label in {left: 'يسار', top: 'أعلى', bottom: 'أسفل', right: 'يمين'} %}
<twig:HoverCard openDelay="10" closeDelay="100">
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>{{ label }}</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content side="{{ side }}" class="flex w-64 flex-col gap-1">
<div class="font-semibold">سماعات لاسلكية</div>
<div class="text-sm text-muted-foreground">٩٩.٩٩ $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{% endfor %}
</div>
{# Hebrew #}
<div class="flex flex-wrap justify-center gap-2" dir="rtl">
{% for side, label in {left: 'שמאל', top: 'למעלה', bottom: 'למטה', right: 'ימין'} %}
<twig:HoverCard openDelay="10" closeDelay="100">
<twig:HoverCard:Trigger>
<twig:Button variant="outline" {{ ...hover_card_trigger_attrs }}>{{ label }}</twig:Button>
</twig:HoverCard:Trigger>
<twig:HoverCard:Content side="{{ side }}" class="flex w-64 flex-col gap-1">
<div class="font-semibold">אוזניות אלחוטיות</div>
<div class="text-sm text-muted-foreground">99.99 $</div>
</twig:HoverCard:Content>
</twig:HoverCard>
{% endfor %}
</div>
</div>
API Reference
Component 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 |
Component HoverCard:Content
| Prop | Type | Description |
|---|---|---|
side |
'bottom'|'top'|'left'|'right' |
The side where the content appears. Defaults to bottom |
| Block | Description |
|---|---|
content |
The content revealed on hover |
Component HoverCard:Trigger
| Block | Description |
|---|---|
content |
The element that reveals the hover card on hover or focus |