Hover Card
A preview of what is behind a link, for sighted mouse users. Opens on hover after a short delay.
import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@/components/ui/hover-card";
export default function Demo() { return ( <HoverCard> <HoverCardTrigger render={ <a href="#dewi" className="text-sm underline underline-offset-4" /> } > @dewi </HoverCardTrigger> <HoverCardContent className="w-72"> <div className="flex flex-col gap-1"> <p className="font-medium">Dewi Lestari</p> <p className="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p className="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCardContent> </HoverCard> );}<script setup lang="ts">import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";</script>
<template> <HoverCard> <HoverCardTrigger as-child> <a href="#dewi" class="text-sm underline underline-offset-4">@dewi</a> </HoverCardTrigger> <HoverCardContent class="w-72"> <div class="flex flex-col gap-1"> <p class="font-medium">Dewi Lestari</p> <p class="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p class="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCardContent> </HoverCard></template><script lang="ts"> import * as HoverCard from "$lib/components/ui/hover-card";</script>
<HoverCard.Root> <HoverCard.Trigger href="#dewi" class="text-sm underline underline-offset-4">@dewi</HoverCard.Trigger> <HoverCard.Content class="w-72"> <div class="flex flex-col gap-1"> <p class="font-medium">Dewi Lestari</p> <p class="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p class="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCard.Content></HoverCard.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/hover-cardpnpm dlx shadcn@latest add @edmi-ui/hover-cardyarn dlx shadcn@latest add @edmi-ui/hover-cardbunx shadcn@latest add @edmi-ui/hover-cardPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/hover-cardpnpm dlx shadcn-vue@latest add @edmi-ui/hover-cardyarn dlx shadcn-vue@latest add @edmi-ui/hover-cardbunx shadcn-vue@latest add @edmi-ui/hover-cardPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/hover-card.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/hover-card.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/hover-card.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/hover-card.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Copy the code from Component source into your project and update the import paths to match your aliases.
import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@/components/ui/hover-card";
<HoverCard> <HoverCardTrigger render={ <a href="#dewi" className="text-sm underline underline-offset-4" /> } > @dewi </HoverCardTrigger> <HoverCardContent className="w-72"> <div className="flex flex-col gap-1"> <p className="font-medium">Dewi Lestari</p> <p className="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p className="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCardContent></HoverCard><script setup lang="ts">import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@/components/ui/hover-card";</script>
<template> <HoverCard> <HoverCardTrigger as-child> <a href="#dewi" class="text-sm underline underline-offset-4">@dewi</a> </HoverCardTrigger> <HoverCardContent class="w-72"> <div class="flex flex-col gap-1"> <p class="font-medium">Dewi Lestari</p> <p class="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p class="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCardContent> </HoverCard></template><script lang="ts"> import * as HoverCard from "$lib/components/ui/hover-card";</script>
<HoverCard.Root> <HoverCard.Trigger href="#dewi" class="text-sm underline underline-offset-4">@dewi</HoverCard.Trigger> <HoverCard.Content class="w-72"> <div class="flex flex-col gap-1"> <p class="font-medium">Dewi Lestari</p> <p class="text-[13px] text-muted-foreground"> Builds AI and megacap baskets. 3 indexes, 412 holders. </p> <p class="text-xs text-muted-foreground">Joined Sep 2026</p> </div> </HoverCard.Content></HoverCard.Root>Component source
Section titled “Component source”import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card";import { cn } from "cn";
function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) { return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />;}
function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) { return ( <PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} /> );}
function HoverCardContent({ className, side = "bottom", sideOffset = 6, align = "center", alignOffset = 4, ...props}: PreviewCardPrimitive.Popup.Props & Pick< PreviewCardPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" >) { return ( <PreviewCardPrimitive.Portal data-slot="hover-card-portal"> <PreviewCardPrimitive.Positioner align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} className="isolate z-50" > <PreviewCardPrimitive.Popup data-slot="hover-card-content" className={cn( "z-50 w-64 origin-(--transform-origin) rounded-xl border border-border bg-popover p-3 text-sm text-popover-foreground outline-hidden transition-[opacity,scale] duration-100 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0", className, )} {...props} /> </PreviewCardPrimitive.Positioner> </PreviewCardPrimitive.Portal> );}
export { HoverCard, HoverCardContent, HoverCardTrigger };<script setup lang="ts">import type { HoverCardRootEmits, HoverCardRootProps } from 'reka-ui'import { HoverCardRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<HoverCardRootProps>()const emits = defineEmits<HoverCardRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <HoverCardRoot v-slot="slotProps" data-slot="hover-card" v-bind="forwarded" > <slot v-bind="slotProps" /> </HoverCardRoot></template><script setup lang="ts">import type { HoverCardContentProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { HoverCardContent, HoverCardPortal, useForwardProps,} from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
defineOptions({ inheritAttrs: false,})
const props = withDefaults( defineProps<HoverCardContentProps & { class?: HTMLAttributes['class'] }>(), { side: 'bottom', sideOffset: 6, align: 'center', alignOffset: 4, },)
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <HoverCardPortal> <HoverCardContent data-slot="hover-card-content" v-bind="{ ...$attrs, ...forwardedProps }" :class=" cn( 'z-50 w-64 origin-(--reka-hover-card-content-transform-origin) rounded-xl border border-border bg-popover p-3 text-sm text-popover-foreground outline-hidden data-[state=open]:animate-in 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', props.class, ) " > <slot /> </HoverCardContent> </HoverCardPortal></template><script setup lang="ts">import type { HoverCardTriggerProps } from 'reka-ui'import { HoverCardTrigger } from 'reka-ui'
const props = defineProps<HoverCardTriggerProps>()</script>
<template> <HoverCardTrigger data-slot="hover-card-trigger" v-bind="props" > <slot /> </HoverCardTrigger></template>export { default as HoverCard } from "./HoverCard.vue";export { default as HoverCardContent } from "./HoverCardContent.vue";export { default as HoverCardTrigger } from "./HoverCardTrigger.vue";<script lang="ts"> import { LinkPreview as HoverCardPrimitive } from "bits-ui"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import HoverCardPortal from "./hover-card-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, side = "bottom", sideOffset = 6, align = "center", alignOffset = 4, portalProps, ...restProps }: HoverCardPrimitive.ContentProps & { alignOffset?: number; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof HoverCardPortal>>; } = $props();</script>
<HoverCardPortal {...portalProps}> <HoverCardPrimitive.Content bind:ref data-slot="hover-card-content" {side} {sideOffset} {align} {alignOffset} class={cn( "z-50 w-64 origin-(--bits-link-preview-content-transform-origin) rounded-xl border border-border bg-popover p-3 text-sm text-popover-foreground outline-hidden data-[state=open]:animate-in 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", className )} {...restProps} /></HoverCardPortal><script lang="ts"> import { LinkPreview as Primitive } from "bits-ui";
let { ...restProps }: Primitive.PortalProps = $props();</script>
<Primitive.Portal {...restProps} /><script lang="ts"> import { LinkPreview as Primitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: Primitive.TriggerProps = $props();</script>
<Primitive.Trigger bind:ref data-slot="hover-card-trigger" {...restProps} /><script lang="ts"> import { LinkPreview as HoverCardPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: HoverCardPrimitive.RootProps = $props();</script>
<HoverCardPrimitive.Root bind:open {...restProps} />import Root from "./hover-card.svelte";import Content from "./hover-card-content.svelte";import Portal from "./hover-card-portal.svelte";import Trigger from "./hover-card-trigger.svelte";
export { Content, Content as HoverCardContent, Portal, Portal as HoverCardPortal, Root, Root as HoverCard, Trigger, Trigger as HoverCardTrigger,};| Prop | Type | Default |
|---|---|---|
HoverCard delay / closeDelay |
number (ms) |
Base UI defaults |
HoverCardContent side / align |
positioner props | bottom / center |
Parts: HoverCard, HoverCardTrigger, HoverCardContent. Built on Base UI PreviewCard.
✦ Edmi additions
Section titled “✦ Edmi additions”- Hard 4px lip, solid popover surface, no blur.