Skip to content
Destyler Destyler Destyler

Hover Card

An hover card allows sighted users to preview content available behind a link.

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/hover-card @destyler/vue
Terminal window
      
        
npm install @destyler/hover-card @destyler/react
Terminal window
      
        
npm install @destyler/hover-card @destyler/svelte
Terminal window
      
        
npm install @destyler/hover-card @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as hoverCard from '@destyler/hover-card'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(hoverCard.machine({ id: useId() }))
const api = computed(() => hoverCard.connect(state.value, send, normalizeProps))
</script>
<template>
<a v-bind="api.getTriggerProps()"></a>
<div v-bind="api.getPositionerProps()">
<div v-bind="api.getContentProps()" >
<div v-bind="api.getArrowProps()">
<div v-bind="api.getArrowTipProps()" />
</div>
</div>
</div>
</template>
import * as hoverCard from '@destyler/hover-card'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId } from 'react'
export default function HoverCard() {
const [state, send] = useMachine(hoverCard.machine({
id: useId(),
}))
const api = hoverCard.connect(state, send, normalizeProps)
return (
<a {...api.getTriggerProps()}></a>
<div {...api.getPositionerProps()}>
<div {...api.getContentProps()}>
<div {...api.getArrowProps()}>
<div {...api.getArrowTipProps()} />
</div>
</div>
</div>
)
}
<script lang="ts">
import * as image from '@destyler/image'
import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(image.machine({ id }))
const api = $derived(image.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<img {...api.getImageProps()} />
<div {...api.getFallbackProps()} ></div>
</div>
import * as hoverCard from '@destyler/hover-card'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
import { Portal } from 'solid-js/web'
export default function HoverCard() {
const [state, send] = useMachine(hoverCard.machine({
id: createUniqueId(),
}))
const api = createMemo(() => hoverCard.connect(state, send, normalizeProps))
return (
<a {...api().getTriggerProps()}></a>
<div {...api().getPositionerProps()}>
<div {...api().getContentProps()}>
<div {...api().getArrowProps()}>
<div {...api().getArrowTipProps()} />
</div>
</div>
</div>
)
}

Making it opened initially

To make a hover card open by default without taking ownership of state, use defaultOpen. Prefer this over seeding with open (which is presence-controlled after Phase 2).

const [state, send] = useMachine(
hoverCard.machine({
defaultOpen: true,
}),
)

Controlled usage

Hover card open state follows the same pattern as popover. Use defaultOpen for an uncontrolled seed; pass open + onOpenChange (and sync via context) when you need controlled open. See Controllable State.

hoverCard.machine({
id: "1",
defaultOpen: false,
})
const open = false // your reactive state
hoverCard.machine({
id: "1",
open,
onOpenChange(details) {
// details => { open: boolean }
},
})

Listening for open state changes

When the hover card is opened or closed, the onOpenChange callback is invoked.

const [state, send] = useMachine(
hoverCard.machine({
onOpenChange(details) {
// details => { open: boolean }
console.log("hovercard is:", details.open ? "opened" : "closed")
},
}),
)

Styling guide

Earlier, we mentioned that each hover card part has a data-part attribute added to them to select and style them in the DOM.

[data-part="trigger"] {
/* styles for trigger */
}
[data-part="content"] {
/* styles for content */
}

Open and closed state

The hover card exposes a data-state attribute that can be used to style the hover card based on its open-close state.

[data-part="trigger"][data-state="open"] {
/* styles for open or closed state */
}
[data-part="content"][data-state="open"] {
/* styles for open or closed state */
}

Arrow

exposes some variable that can be used to style the arrow.

[data-part="arrow"] {
/* styles for arrow */
--arrow-background: white;
--arrow-size: 8px;
}

Methods and Properties

Machine Context

The hover card machine exposes the following context properties:

ids
Partial<{ trigger: string; content: string; positioner: string; arrow: string; }>
The ids of the elements in the popover. Useful for composition.
onOpenChange
(details: OpenChangeDetails) => void
Function called when the hover card opens or closes.
openDelay(default: 700)
number
The duration from when the mouse enters the trigger until the hover card opens.
closeDelay(default: 300)
number
The duration from when the mouse leaves the trigger or content until the hover card closes.
open
boolean
Whether the hover card is open
defaultOpen
boolean
The initial open state of the hover card when it is not controlled. Prefer this over seeding with `open` for uncontrolled usage.
positioning
PositioningOptions
The user provided options used to position the popover content
dir(default: "ltr")
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The hover card api exposes the following methods:

open
boolean
Whether the hover card is open
setOpen
(open: boolean) => void
Function to open the hover card
reposition
(options?: Partial<PositioningOptions>) => void
Function to reposition the popover

Data Attributes

Trigger

attribute
description
data-scope
hover-card
data-part
trigger
data-placement
The placement of the trigger
data-state
"open" | "closed"

Content

attribute
description
data-scope
hover-card
data-part
content
data-state
"open" | "closed"
data-placement
The placement of the content