Askr documentation
UI & Components

Hover Card

Hover Card: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { HoverCard, HoverCardContent, HoverCardTrigger } from '@askrjs/themes/components';

<HoverCard>
  <HoverCardTrigger asChild><a href={ownerUrl}>{owner.name}</a></HoverCardTrigger>
  <HoverCardContent>
    <strong>{owner.name}</strong>
    <p>{owner.projectCount} active projects</p>
  </HoverCardContent>
</HoverCard>

Published props

Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.

HoverCardAsChildProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSX.Element;
closeDelaycloseDelay?: number | undefined;
defaultOpendefaultOpen?: boolean | undefined;
idid?: string | undefined;
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;
openopen?: boolean | undefined;
openDelayopenDelay?: number | undefined;

HoverCardContentAsChildProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild: true;
childrenchildren: JSXElement;
forceMountforceMount?: boolean | undefined;
refref?: Ref<Element>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

HoverCardContentProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;
asChildasChild?: false | undefined;
childrenchildren?: unknown;
forceMountforceMount?: boolean | undefined;
refref?: Ref<HTMLDivElement>;
sideside?: OverlaySide | undefined;
sideOffsetsideOffset?: number | undefined;

HoverCardOwnProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
childrenchildren?: unknown;
closeDelaycloseDelay?: number | undefined;
defaultOpendefaultOpen?: boolean | undefined;
idid?: string | undefined;
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;
openopen?: boolean | undefined;
openDelayopenDelay?: number | undefined;

HoverCardPortalProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
childrenchildren?: unknown;

HoverCardProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
childrenchildren?: unknown;
closeDelaycloseDelay?: number | undefined;
defaultOpendefaultOpen?: boolean | undefined;
idid?: string | undefined;
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;
openopen?: boolean | undefined;
openDelayopenDelay?: number | undefined;

HoverCardTriggerAsChildProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
typetype?: undefined;

HoverCardTriggerProps

Import from @askrjs/ui/hover-card.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
typetype?: "button" | "submit" | "reset" | undefined;

Purpose

Hover Card shows a floating preview when the user hovers a trigger — a user avatar revealing a profile summary, a link showing a preview of its destination — without requiring a click. It's for supplementary context, not primary interaction, since anything hover-only is invisible to touch and keyboard users unless they also have another way to reach the same content.

Install and import

Import from `@askrjs/ui/hover-card` for behavior and `@askrjs/themes/hover-card` for the styled version; both are real subpaths in their packages. As with the other overlays, the pieces are also available from each package's root export.

Live examples

`HoverCard` wraps a `HoverCardTrigger` and a `HoverCardPortal` containing `HoverCardContent`; there's no separate close button component, since dismissal is driven by the pointer leaving rather than an explicit action. Tune `openDelay` and `closeDelay` on the root to avoid the card flickering open on quick mouse passes.

Anatomy

The tree is `HoverCard` > `HoverCardTrigger` and `HoverCardPortal` > `HoverCardContent` — deliberately the smallest of the overlay primitives, since it doesn't need an overlay backdrop, a title/description pair, or a close affordance the way Dialog and Alert Dialog do.

State model

Open state is `open`/`defaultOpen`/`onOpenChange`, same shape as the other overlays, plus two hover-specific timing props: `openDelay` and `closeDelay`, both numbers in milliseconds. Those delays exist because hover intent is noisy — without them, every incidental mouse pass over the trigger would pop the card open.

Keyboard and accessibility

There's no dedicated a11y contract exported for Hover Card the way there is for Dialog and Popover, but it isn't hover-only despite the name: focusing the trigger (via keyboard `Tab`) opens it the same as hovering does, Tab from an open trigger moves into the first focusable element inside `HoverCardContent`, Tab cycling inside the content is handled so it doesn't leak focus back to the page unexpectedly, and Escape returns focus to the trigger and closes it. What it genuinely lacks compared to Popover is a hard focus trap — so still avoid putting something the user *must* interact with in it, but "keyboard users can't reach the content" is not accurate; they can tab into it and back out cleanly.

Styling and tokens

The themed `hover-card.css` styles the content panel much like Popover's — it inherits the same anchored-positioning approach via `side`/`align`/`sideOffset` on `HoverCardContent`, so overrides you've already made to popover positioning tokens carry the same visual language over.

API

`HoverCard` takes `open`, `defaultOpen`, `onOpenChange`, `openDelay`, and `closeDelay`. `HoverCardContent` adds `forceMount`, `side`, `align`, and `sideOffset` on top of div props. `HoverCardTrigger` is button-like with `onPress`/`disabled`; note it renders as a real `<button>` by default even though the interaction is hover-driven, so give it `asChild` if you need to trigger from a link or inline text instead.

Edge cases

Fast mouse movement across multiple triggers in a row can cause hover cards to flicker between open and closed if `closeDelay` is too short — bump it up when triggers are dense, like a list of avatars. There's no hard focus trap the way Dialog has one, so a `Tab` past the last focusable element inside the content is allowed to leave it rather than cycling back — that's a real gap for content the user needs to fully interact with (a form, a multi-step flow), but it's not the same as having no keyboard support at all: focus-driven open/close and Tab/Escape handling are real and wired up.

Popover covers the click-triggered version of this same anchored-panel pattern, and is the safer default when the content needs to be reachable without a mouse. Tooltip is smaller still, for single-line labels rather than rich content.