Keyboard shortcut: Ctrl + K
Get started

Hover card

VHoverCard previews what a link leads to, such as a profile behind a mention, when the pointer or the keyboard focus rests on it. Its content may be interactive.

Usage

vue
<script setup lang="ts">
import { VAvatar, VButton, VHoverCard, VLink, VTypography } from 'vectis-ui'
</script>

<template>
  <VHoverCard>
    <template #default="{ triggerProps }">
      <VLink href="#ada" v-bind="triggerProps">@ada</VLink>
    </template>
    <template #content>
      <div class="header">
        <VAvatar name="Ada Lovelace" size="lg" />
        <div>
          <VTypography variant="subtitle">Ada Lovelace</VTypography>
          <VTypography variant="body-sm" tone="muted">@ada</VTypography>
        </div>
      </div>
      <VTypography variant="body-sm">
        Mathematician. Wrote the first published algorithm for the Analytical Engine.
      </VTypography>
      <div class="actions">
        <VButton size="sm">Follow</VButton>
      </div>
    </template>
  </VHoverCard>
</template>

<style scoped>
.header {
  display: flex;
  align-items: center;
  gap: var(--vectis-space-3);
}
.actions {
  display: flex;
  gap: var(--vectis-space-2);
}
</style>

Examples

In running text

The trigger flows with the sentence. The card content mounts on first opening, so a trigger inside a paragraph still renders valid HTML on the server.

vue

The notes were translated and expanded by Ada Lovelace in 1843.

<script setup lang="ts">
import { VAvatar, VHoverCard, VLink, VTypography } from 'vectis-ui'
</script>

<template>
  <VTypography class="sentence">
    The notes were translated and expanded by
    <VHoverCard>
      <template #default="{ triggerProps }">
        <VLink href="#ada" v-bind="triggerProps">Ada Lovelace</VLink>
      </template>
      <template #content>
        <div class="header">
          <VAvatar name="Ada Lovelace" />
          <VTypography variant="subtitle">Ada Lovelace</VTypography>
        </div>
        <VTypography variant="body-sm">
          English mathematician, born in 1815. Her Note G is often cited as the first computer
          program.
        </VTypography>
      </template>
    </VHoverCard>
    in 1843.
  </VTypography>
</template>

<style scoped>
.sentence {
  max-inline-size: 32rem;
}
.header {
  display: flex;
  align-items: center;
  gap: var(--vectis-space-3);
}
</style>

Delays

openDelay sets how long the trigger must be hovered or focused before the card opens. closeDelay keeps it while the pointer crosses the gap to the card.

vue
<script setup lang="ts">
import { VHoverCard, VLink, VTypography } from 'vectis-ui'

const cards = [
  { label: 'Default (500 / 300 ms)', openDelay: 500, closeDelay: 300 },
  { label: 'Quick (150 / 100 ms)', openDelay: 150, closeDelay: 100 },
]
</script>

<template>
  <div class="row">
    <VHoverCard
      v-for="card in cards"
      :key="card.label"
      :open-delay="card.openDelay"
      :close-delay="card.closeDelay"
    >
      <template #default="{ triggerProps }">
        <VLink href="#vectis" v-bind="triggerProps">{{ card.label }}</VLink>
      </template>
      <template #content>
        <VTypography variant="subtitle">vectis-ui</VTypography>
        <VTypography variant="body-sm" tone="muted">
          A Vue 3 component library built for modern browsers.
        </VTypography>
      </template>
    </VHoverCard>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-6);
}
</style>

Interactive content

Keyboard focus opens the card after the delay and Tab moves into it. Escape closes it from anywhere and returns the focus to the trigger. Taps open nothing, so keep the same information at the trigger’s destination.

vue
<script setup lang="ts">
import { VHoverCard, VLink, VTypography } from 'vectis-ui'
</script>

<template>
  <VHoverCard placement="bottom-start">
    <template #default="{ triggerProps }">
      <VLink href="#repository" v-bind="triggerProps">vectis-ui/vectis-ui</VLink>
    </template>
    <template #content>
      <VTypography variant="subtitle">vectis-ui/vectis-ui</VTypography>
      <VTypography variant="body-sm" tone="muted">
        A Vue 3 component library built for modern browsers.
      </VTypography>
      <nav aria-label="Repository" class="links">
        <VLink href="#issues" underline="hover">Issues</VLink>
        <VLink href="#pulls" underline="hover">Pull requests</VLink>
        <VLink href="#releases" underline="hover">Releases</VLink>
      </nav>
    </template>
  </VHoverCard>
</template>

<style scoped>
.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-4);
}
</style>

Loading on open

v-model:open reports every opening, which is the moment to fetch the card’s data. Setting it opens or closes the card without delay.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VAvatar, VHoverCard, VLink, VSkeletonLoader, VTypography } from 'vectis-ui'

const open = ref(false)
const profile = ref<{ name: string; bio: string } | null>(null)

async function onOpen(value: boolean) {
  if (!value || profile.value) return
  // Stands in for a request to your API.
  await new Promise((resolve) => setTimeout(resolve, 800))
  profile.value = {
    name: 'Grace Hopper',
    bio: 'Computer scientist. Led the team behind the first compiler for a programming language.',
  }
}
</script>

<template>
  <VHoverCard v-model:open="open" @update:open="onOpen">
    <template #default="{ triggerProps }">
      <VLink href="#grace" v-bind="triggerProps">@grace</VLink>
    </template>
    <template #content>
      <div v-if="!profile" class="loading">
        <VSkeletonLoader shape="circle" size="lg" />
        <VSkeletonLoader :lines="2" />
      </div>
      <template v-else>
        <div class="header">
          <VAvatar :name="profile.name" size="lg" />
          <VTypography variant="subtitle">{{ profile.name }}</VTypography>
        </div>
        <VTypography variant="body-sm">{{ profile.bio }}</VTypography>
      </template>
    </template>
  </VHoverCard>
</template>

<style scoped>
.loading {
  display: grid;
  gap: var(--vectis-space-2);
  inline-size: 16rem;
}
.header {
  display: flex;
  align-items: center;
  gap: var(--vectis-space-3);
}
</style>

API

Props

PropTypeDefault
placementHoverCardPlacement'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end''bottom'
Preferred position; flips when space is insufficient.
openDelaynumber500
Hover or keyboard focus wait before opening, in milliseconds.
closeDelaynumber300
Wait before closing once the pointer has left the trigger and the card, in milliseconds.
v-model:openbooleanfalse
Open state. Changes apply without delay.

Slots

SlotType
default{ triggerProps: HoverCardTriggerProps; }
Trigger, usually a link. Bind the supplied triggerProps to set aria-details.
content{}
Card content, mounted on first opening. May contain links and buttons.

Types

Types used in the API tables. Import exported types from vectis-ui.

export type HoverCardTriggerProps = {
  'aria-details': string
}

CSS variables

TokenValue
--vectis-control-size-hover-card-max20rem