Hover Card

For sighted users to preview content available behind a link.

Examples

Basic

PropDefaultTypeDescription
closeDelay300numberThe duration from when the mouse leaves the trigger or content until the hover card closes.
defaultOpenfalsebooleanThe open state of the hover card when it is initially rendered. Use when you do not need to control its open state.
open-booleanThe controlled open state of the hover card. Can be binded as v-model:open.
openDelay700numberThe duration from when the mouse enters the trigger until the hover card opens.
Preview
Code

Variant and Color

PropDefaultTypeDescription
hovercardoutline-gray{variant}-{color}Set the hover-card variant and color.
Preview
Code

Arrow

PropDefaultTypeDescription
arrowfalsebooleanSet the arrow that render alongside the hover card.
Arrow PropDefaultTypeDescription
height6numberThe height of the arrow in pixels.
width12numberThe width of the arrow in pixels.
rounded-booleanWhen true, render the rounded version of arrow. Do not work with as or asChild
Preview
Code

Slots

NamePropsDescription
default-Allows advanced customization using sub-components, replacing the default hover-card structure.
triggeropenThe trigger slot. Receives the current open state
content-The content slot to display the entire content of the card.

Custom Rendering

Use the default slot for full control over the hover-card's structure. This allows you to compose the hover-card using its individual sub-components (like NHoverCardContent, NHoverCardTrigger, etc., listed in the Components section), similar to libraries like shadcn/ui.

Preview
Code

Presets

shortcuts/hover-card.ts
type HoverCardPrefix = 'hover-card'

export const staticHoverCard: Record<`${HoverCardPrefix}-${string}` | HoverCardPrefix, string> = {
  // configurations
  'hover-card': '',
  'hover-card-default-variant': 'outline-gray',

  // components
  'hover-card-trigger': '',
  'hover-card-content': 'bg-base text-popover animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 rounded-md border p-4 shadow-md outline-hidden',
  'hover-card-arrow': '!bg-transparent !border-none -mt-1px fill-base stroke-base',

  // static variants
  'hover-card-soft-gray': 'bg-muted border border-base',
  'hover-card-outline-gray': 'bg-base border border-base',
}

export const dynamicHoverCard = [
  [/^hover-card-soft(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-50 fill-${c}-50 stroke-${c}-200 border-${c}-200 dark:(bg-${c}-900 border-${c}-700/58 fill-${c}-900 stroke-${c}-700/58)`],
  [/^hover-card-outline(-(\S+))?$/, ([, , c = 'gray']) => `border stroke-${c}-200 border-${c}-200 dark:(border-${c}-700/58 stroke-${c}-700/58)`],
]

export const hoverCard = [
  ...dynamicHoverCard,
  staticHoverCard,
]

Props

types/hover-card.ts
import type { HoverCardArrowProps, HoverCardContentProps, HoverCardRootProps, HoverCardTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

interface BaseExtensions {
  /** CSS class for the component */
  class?: HTMLAttributes['class']
  /** Size of the component */
  size?: HTMLAttributes['class']
}

export interface NHoverCardProps extends HoverCardRootProps, Omit<NHoverCardTriggerProps, 'una' | 'size'>, BaseExtensions {
  /**
   * Allows you to add `UnaUI` hover-card preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/hover-card.ts
   * @example
   * hover-card="outline-gray"
   */
  hoverCard?: HTMLAttributes['class']
  /** Whether to show the arrow or not */
  arrow?: boolean

  /** Props for the hover card content */
  _hoverCardContent?: Partial<NHoverCardContentProps>
  /** Props for the hover card trigger */
  _hoverCardTrigger?: Partial<NHoverCardTriggerProps>
  /** Props for the hover card arrow */
  _hoverCardArrow?: Partial<NHoverCardArrowProps>

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/hover-card.ts
   */
  una?: NHoverCardUnaProps
}

export interface NHoverCardContentProps extends HoverCardContentProps, Pick<NHoverCardProps, 'hoverCard'>, BaseExtensions {
  /** Additional properties for the una component */
  una?: Pick<NHoverCardUnaProps, 'hoverCardContent'>
}

export interface NHoverCardTriggerProps extends HoverCardTriggerProps, Omit<NButtonProps, 'una'> {
  /** Additional properties for the una component */
  una?: Pick<NHoverCardUnaProps, 'hoverCardTrigger'> & NButtonProps['una']
}

export interface NHoverCardArrowProps extends HoverCardArrowProps, Pick<NHoverCardProps, 'hoverCard'>, BaseExtensions {
  /** Additional properties for the una component */
  una?: Pick<NHoverCardUnaProps, 'hoverCardArrow'>
}

interface NHoverCardUnaProps {
  /** CSS class for the hover card */
  hoverCard?: HTMLAttributes['class']
  /** CSS class for the hover card content */
  hoverCardContent?: HTMLAttributes['class']
  /** CSS class for the hover card trigger */
  hoverCardTrigger?: HTMLAttributes['class']
  /** CSS class for the hover card arrow */
  hoverCardArrow?: HTMLAttributes['class']
}

Components

HoverCard.vue
HoverCardContent.vue
HoverCardArrow.vue
HoverCardContent.vue
HoverCardTrigger.vue
<script setup lang="ts">
import type { HoverCardRootEmits } from 'reka-ui'
import type { NHoverCardProps } from '../../types'
import { reactivePick } from '@vueuse/core'
import { HoverCardRoot, useForwardPropsEmits } from 'reka-ui'
import HoverCardArrow from './HoverCardArrow.vue'
import HoverCardContent from './HoverCardContent.vue'
import HoverCardTrigger from './HoverCardTrigger.vue'

const props = withDefaults(defineProps<NHoverCardProps>(), {
  hoverCard: 'outline-gray',
})
const emits = defineEmits<HoverCardRootEmits>()

const rootProps = reactivePick(props, [
  'closeDelay',
  'openDelay',
  'defaultOpen',
  'open',
])

const forwarded = useForwardPropsEmits(rootProps, emits)
</script>

<template>
  <HoverCardRoot
    v-slot="{ open }"
    v-bind="forwarded"
  >
    <slot>
      <HoverCardTrigger
        as-child
        :una
        v-bind="_hoverCardTrigger"
      >
        <slot name="trigger" :open />
      </HoverCardTrigger>
      <HoverCardContent v-bind="_hoverCardContent" :hover-card :una>
        <slot name="content" />
        <HoverCardArrow v-if="props.arrow" v-bind="_hoverCardArrow" :hover-card :una />
      </HoverCardContent>
    </slot>
  </HoverCardRoot>
</template>