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
<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.
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.
<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.
<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.
<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
Slots
Types
Types used in the API tables. Import exported types from vectis-ui.
export type HoverCardTriggerProps = {
'aria-details': string
}CSS variables