Hover Card
A popup card that appears when the user hovers over a trigger element, displaying additional information or actions without leaving the current context.
Docsimport { HoverCard, HoverCardTrigger, HoverCardContent } from "~/components/hover-card";
export default function HoverCardBasicDemo() {
return (
<HoverCard>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardContent useArrow>
<div>Content displayed on hover</div>
</HoverCardContent>
</HoverCard>
);
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add hover-cardManual
Create the recipe file at src/components/recipes/hover-card.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const hoverCardVariants = tv({
slots: {
root: "inline-flex",
trigger:
"inline-flex items-center justify-center gap-2 rounded-md border border-input bg-transparent px-4 py-2 text-sm font-medium text-foreground ring-offset-background transition-colors hover:bg-accent max-sm:select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
positioner: "",
content:
"z-[100] w-72 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg animate-in fade-in-0 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",
arrow: "[--arrow-size:10px]",
arrowTip: "h-2 w-2 border-t border-l border-border bg-popover",
},
});
export type HoverCardVariants = VariantProps<typeof hoverCardVariants>;Create the component directory and files.
src/components/hover-card/hover-card.base.tsx:
import { HoverCard as ArkHoverCard } from "@ark-ui/solid/hover-card";
import { splitProps, type Component } from "solid-js";
import { Portal } from "solid-js/web";
import { buttonVariants, hoverCardVariants, type ButtonVariants } from "../recipes/hover-card";
const styles = hoverCardVariants();
const Root = ArkHoverCard.Root;
const RootProvider = ArkHoverCard.RootProvider;
const Trigger: Component<ArkHoverCard.TriggerProps & ButtonVariants> = (props) => {
const [local, others] = splitProps(props, ["class", "variant", "size"]);
return (
<ArkHoverCard.Trigger
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
{...others}
/>
);
};
const Positioner: Component<ArkHoverCard.PositionerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<Portal>
<ArkHoverCard.Positioner class={styles.positioner({ class: local.class })} {...others} />
</Portal>
);
};
const Content: Component<ArkHoverCard.ContentProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkHoverCard.Content class={styles.content({ class: local.class })} {...others} />;
};
const Arrow: Component<ArkHoverCard.ArrowProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkHoverCard.Arrow class={styles.arrow({ class: local.class })} {...others} />;
};
const ArrowTip: Component<ArkHoverCard.ArrowTipProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkHoverCard.ArrowTip class={styles.arrowTip({ class: local.class })} {...others} />;
};
export const HoverCard = {
Root,
RootProvider,
Trigger,
Positioner,
Content,
Arrow,
ArrowTip,
};src/components/hover-card/index.tsx:
import { splitProps, type Component } from "solid-js";
import { HoverCard as HoverCardBase } from "./hover-card.base";
import { HoverCard as ArkHoverCard } from "@ark-ui/solid/hover-card";
const HoverCard = HoverCardBase.Root;
const HoverCardTrigger = HoverCardBase.Trigger;
type HoverCardContentProps = ArkHoverCard.ContentProps & {
/** When true, renders an arrow pointing to the trigger element */
useArrow?: boolean;
};
const HoverCardContent: Component<HoverCardContentProps> = (props) => {
const [local, others] = splitProps(props, ["useArrow", "children"]);
return (
<HoverCardBase.Positioner>
<HoverCardBase.Content {...others}>
{local.useArrow && (
<HoverCardBase.Arrow>
<HoverCardBase.ArrowTip />
</HoverCardBase.Arrow>
)}
{local.children}
</HoverCardBase.Content>
</HoverCardBase.Positioner>
);
};
export { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardBase };
export { hoverCardVariants, type HoverCardVariants } from "../recipes/hover-card";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Dependencies: This component imports shared recipes or sub-components from other packages. Make sure the following are also installed: button.
Usage
Basic Usage
A basic hover card that displays preview content on hover.
import { HoverCard, HoverCardTrigger, HoverCardContent } from "~/components/hover-card";
export default function HoverCardBasicDemo() {
return (
<HoverCard>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardContent useArrow>
<div>Content displayed on hover</div>
</HoverCardContent>
</HoverCard>
);
}Controlled Open State
Use open and onOpenChange to control the hover card visibility externally.
Open: false
import { createSignal } from "solid-js";
import { HoverCard, HoverCardTrigger, HoverCardContent } from "~/components/hover-card";
export default function HoverCardControlledDemo() {
const [open, setOpen] = createSignal(false);
return (
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground">Open: {open() ? "true" : "false"}</p>
<HoverCard open={open()} onOpenChange={(e) => setOpen(e.open)}>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardContent>
<div class="text-sm">Controlled hover card</div>
</HoverCardContent>
</HoverCard>
</div>
);
}Custom Delay
Adjust the hover open and close delays using openDelay and closeDelay.
Custom delay (200ms open, 100ms close)
import { HoverCard, HoverCardTrigger, HoverCardContent } from "~/components/hover-card";
export default function HoverCardDelayDemo() {
return (
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground mb-2">Custom delay (200ms open, 100ms close)</p>
<HoverCard openDelay={200} closeDelay={100}>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardContent>
<div class="text-sm">Custom delay hover card</div>
</HoverCardContent>
</HoverCard>
</div>
);
}Custom Positioning
Position the hover card at different sides using the positioning prop.
Positioned to the right
import { HoverCard, HoverCardTrigger, HoverCardContent } from "~/components/hover-card";
export default function HoverCardPositioningDemo() {
return (
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground mb-2">Positioned to the right</p>
<HoverCard positioning={{ placement: "right" }}>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardContent>
<div class="text-sm">Positioned to the right</div>
</HoverCardContent>
</HoverCard>
</div>
);
}Advanced Usage
When the composite HoverCard does not provide enough control, import the raw primitive parts via HoverCardBase:
import { HoverCardBase } from "~/components/hover-card";Or import directly from the base file:
import { HoverCard } from "~/components/hover-card/hover-card.base";Root Provider
For machine-controlled usage with useHoverCard and HoverCardBase.RootProvider.
RootProvider pattern
import { useHoverCard } from "@ark-ui/solid/hover-card";
import { HoverCardBase } from "~/components/hover-card";
export default function HoverCardRootProviderDemo() {
const machine = useHoverCard({ openDelay: 200, closeDelay: 100 });
return (
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground mb-2">RootProvider pattern</p>
<HoverCardBase.RootProvider value={machine}>
<HoverCardBase.Trigger>Hover me</HoverCardBase.Trigger>
<HoverCardBase.Positioner>
<HoverCardBase.Content>
<div class="text-sm">Content controlled via machine</div>
</HoverCardBase.Content>
</HoverCardBase.Positioner>
</HoverCardBase.RootProvider>
</div>
);
}Custom Arrow
For custom arrow styling or positioning, compose the parts manually using HoverCardBase.
Custom arrow size
import { HoverCard, HoverCardTrigger, HoverCardBase } from "~/components/hover-card";
export default function HoverCardArrowDemo() {
return (
<div class="flex flex-col items-center gap-4">
<p class="text-sm text-muted-foreground mb-2">Custom arrow size</p>
<HoverCard>
<HoverCardTrigger>Hover me</HoverCardTrigger>
<HoverCardBase.Positioner>
<HoverCardBase.Content>
<HoverCardBase.Arrow class="[--arrow-size:14px]">
<HoverCardBase.ArrowTip />
</HoverCardBase.Arrow>
<div class="text-sm">Custom arrow size</div>
</HoverCardBase.Content>
</HoverCardBase.Positioner>
</HoverCard>
</div>
);
}API Reference
See the Ark UI Hover Card documentation.