import type { FC, JSX, PropsWithChildren } from 'hono/jsx'
import { cn } from '@/lib/utils'
type Side = 'top' | 'right' | 'bottom' | 'left'
type Align = 'start' | 'center' | 'end'
type HoverCardProps = PropsWithChildren<{
id: string
side?: Side
align?: Align
openDelay?: number
closeDelay?: number
class?: string
}>
type HoverCardTriggerProps = JSX.IntrinsicElements['a'] & {
cardId: string
}
type HoverCardContentProps = JSX.IntrinsicElements['div']
export const HoverCard: FC<HoverCardProps> = ({
id,
side = 'bottom',
align = 'center',
openDelay = 500,
closeDelay = 300,
class: className,
children,
}) => (
<div
data-hover-card={id}
data-hover-card-side={side}
data-hover-card-align={align}
data-hover-card-open-delay={openDelay}
data-hover-card-close-delay={closeDelay}
data-state="closed"
hidden
class={cn(
'z-50 w-64 rounded-xl bg-popover p-4 text-foreground shadow-md outline-none',
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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
)}
>
{children}
</div>
)
export const HoverCardTrigger: FC<HoverCardTriggerProps> = ({
cardId,
class: className,
children,
...props
}) => (
<a
data-hover-card-trigger={cardId}
class={cn(
'inline-flex cursor-pointer',
className
)}
{...props}
>
{children}
</a>
)
export const HoverCardContent: FC<HoverCardContentProps> = ({
class: className,
children,
...props
}) => (
<div
data-hover-card-content
class={cn('', className)}
{...props}
>
{children}
</div>
)
Installation
Initialize your project
First time only. Sets up config and installs base dependencies.
npx @kiwa-ui/cli initAdd the component
This will install the component and any dependencies it needs.
npx @kiwa-ui/cli add hover-cardInstall dependencies
Add the required npm packages.
pnpm add @kiwa-ui/enhanceAdd the source file
Add this file to your project.
import type { FC, JSX, PropsWithChildren } from 'hono/jsx'
import { cn } from '@/lib/utils'
type Side = 'top' | 'right' | 'bottom' | 'left'
type Align = 'start' | 'center' | 'end'
type HoverCardProps = PropsWithChildren<{
id: string
side?: Side
align?: Align
openDelay?: number
closeDelay?: number
class?: string
}>
type HoverCardTriggerProps = JSX.IntrinsicElements['a'] & {
cardId: string
}
type HoverCardContentProps = JSX.IntrinsicElements['div']
export const HoverCard: FC<HoverCardProps> = ({
id,
side = 'bottom',
align = 'center',
openDelay = 500,
closeDelay = 300,
class: className,
children,
}) => (
<div
data-hover-card={id}
data-hover-card-side={side}
data-hover-card-align={align}
data-hover-card-open-delay={openDelay}
data-hover-card-close-delay={closeDelay}
data-state="closed"
hidden
class={cn(
'z-50 w-64 rounded-xl bg-popover p-4 text-foreground shadow-md outline-none',
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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
)}
>
{children}
</div>
)
export const HoverCardTrigger: FC<HoverCardTriggerProps> = ({
cardId,
class: className,
children,
...props
}) => (
<a
data-hover-card-trigger={cardId}
class={cn(
'inline-flex cursor-pointer',
className
)}
{...props}
>
{children}
</a>
)
export const HoverCardContent: FC<HoverCardContentProps> = ({
class: className,
children,
...props
}) => (
<div
data-hover-card-content
class={cn('', className)}
{...props}
>
{children}
</div>
)
Usage
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from '@/components/ui/hover-card'
<HoverCardTrigger cardId='profile'>@username</HoverCardTrigger>
<HoverCard id='profile' side='bottom' align='start'>
<HoverCardContent>
<p class='text-sm'>Full-stack developer.</p>
</HoverCardContent>
</HoverCard>Interactivity
This component is SSR-first and works without client JavaScript. Add @kiwa-ui/enhance for interactive behavior like toggling, keyboard navigation, and ARIA state management.
Add to your layout
<script type="module">
import { hoverCard } from '@kiwa-ui/enhance'
hoverCard()
</script>The card content stays hidden and the trigger renders as a plain element with no hover or focus behavior. Any information in the card is not accessible without the script.
Hovering or focusing the trigger opens the card with a configurable delay (default 500ms open, 300ms close). The card is positioned relative to the trigger and dismisses on mouse leave or blur.