Hover Card
HoverCard is a headless component that displays supplementary content when triggered, typically by a hover or focus interaction on a trigger element. It conditionally renders a container with role="tooltip" and an accessible label when the open prop is true.
This component is useful for user profile previews, link previews, contextual help, or any popover-style content that should appear on hover or focus. The open prop is bindable, allowing the consumer to control visibility from hover/focus event handlers on a trigger element. Consumers provide the card content as children and manage the show/hide logic externally.
Example
Show demo markup
<div class="hover-card"><p>Card shown on hover</p></div>
Usage example
<script lang="ts">
import HoverCard from "./HoverCard.svelte";
let open = $state(false);
</script>
<button
onmouseenter={() => (open = true)}
onmouseleave={() => (open = false)}
onfocus={() => (open = true)}
onblur={() => (open = false)}
aria-describedby="user-card"
>Hover me</button>
<HoverCard id="user-card" label="User info" bind:open>
<p>Profile details here</p>
</HoverCard>
Show Svelte source
// In your Svelte component:
import HoverCard from "lily-design-system-svelte-headless/components/HoverCard/HoverCard.svelte";
<HoverCard>
<!-- content -->
</HoverCard>
Usage
Basic hover card with a trigger button:
<button
onmouseenter={() => open = true}
onmouseleave={() => open = false}
onfocus={() => open = true}
onblur={() => open = false}
>
Hover me
</button>
<HoverCard label="User info" open={open}>
<p>Profile details here</p>
</HoverCard>
User profile preview on a username link:
<a
href="/users/jdoe"
onmouseenter={() => profileOpen = true}
onmouseleave={() => profileOpen = false}
onfocus={() => profileOpen = true}
onblur={() => profileOpen = false}
>
@jdoe
</a>
<HoverCard label="Profile preview for jdoe" open={profileOpen}>
<Avatar label="Jane Doe">
<AvatarImage src="/avatars/jdoe.jpg" alt="Jane Doe" />
</Avatar>
<strong>Jane Doe</strong>
<p>Software Engineer at Acme Corp</p>
<p>12 projects · 48 contributions</p>
</HoverCard>
Link preview hover card:
<a
href="https://example.com/article"
onmouseenter={() => linkPreviewOpen = true}
onmouseleave={() => linkPreviewOpen = false}
onfocus={() => linkPreviewOpen = true}
onblur={() => linkPreviewOpen = false}
>
Understanding Accessibility
</a>
<HoverCard label="Article preview" open={linkPreviewOpen}>
<Image src="/thumbnails/a11y.jpg" alt="Accessibility article thumbnail" />
<strong>Understanding Accessibility</strong>
<p>A practical guide to building inclusive web applications.</p>
</HoverCard>
Props
label: string (required) -- accessible name for the hover card via aria-label
open: boolean (default: false) -- whether the hover card is currently visible; bindable with two-way open binding
children: slot (required) -- content to display inside the hover card
...restProps: unknown -- additional attributes spread onto the container <div>
Keyboard Interactions
None at the component level. The consumer manages focus-based visibility on the trigger element.
ARIA
role="tooltip" -- identifies the hover card as supplementary descriptive content
aria-label={label} -- provides an accessible name describing the hover card content
When to Use
- Use for user profile previews triggered by hovering over a username or avatar.
- Use for link previews that show a summary of the destination page.
- Use for contextual help or supplementary information triggered by hover or focus.
- Use when the content is supplementary and does not require user interaction to dismiss.
When Not to Use
- Do not use for content that requires user interaction such as buttons or forms -- use Popover instead.
- Do not use for simple text descriptions -- use Tooltip instead.
- Do not use as the only way to access content -- hover is not available on touch devices.
- Do not use for essential information the user must see -- place it inline instead.
Headless
This headless component provides a <div> with role="tooltip" and aria-label that conditionally renders when open is true. The consumer provides all visual styling including positioning, shadows, borders, arrow indicators, and content layout.
Styles
The consumer provides all CSS styling. The component renders with a .hover-card class for targeting. No default styles are included — this is a fully headless component.
Testing
- Verify the component renders a
<div> element with class hover-card
- Verify role="tooltip"` -- identifies the hover card as supplementary descriptive content
- Verify aria-label={label}` -- provides an accessible name describing the hover card content
- Verify pass-through attributes are applied
Advice
- Designers: Add a slight delay before showing and hiding the card to prevent flickering on accidental hover. Include an arrow pointing to the trigger element.
- Developers: Manage hover/focus events on the trigger element to toggle
open. Consider adding a delay to prevent the card from disappearing when the user moves the mouse between the trigger and the card.
Implementation Notes
- Conditionally renders a
<div> with role="tooltip" when open is true
- Uses
aria-label for an accessible name describing the card content
- Supports two-way binding on the
open prop
- Accepts a
children slot for flexible card content
- Spreads
restProps onto the container for consumer customization
- The consumer is responsible for managing hover/focus events on the trigger element to toggle
open
Related components
popover — a floating content box anchored to a trigger element
tooltip — a small popup showing descriptive text on hover or focus
popup — a temporary overlay that appears above page content
References
- WAI-ARIA tooltip role: https://www.w3.org/TR/wai-aria-1.2/#tooltip
- WAI-ARIA Tooltip Pattern: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: hover-card
- PascalCase: HoverCard
- Description: a card that appears on hover over a trigger element
- Status: stable — exercised in composed page flows under e2e and axe, beyond the per-component checks
- HTML tag: <div>
- CSS class: .hover-card
- Interactive: no
ARIA
role="tooltip" -- identifies the hover card as supplementary descriptive content aria-label={label} -- provides an accessible name describing the hover card content
Keyboard
- No keyboard interactions — this is a passive element
Props
label: string (required) -- accessible name for the hover card via aria-label open: boolean (default: false) -- whether the hover card is currently visible; bindable with two-way open binding children: slot (required) -- content to display inside the hover card ...restProps: unknown -- additional attributes spread onto the container <div>