Hover Card
Preview content behind a link. On mobile, triggered by long-press.
Web preview — components render natively on iOS & Android
import { Text } from "react-native";
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from "@/components/ui/hover-card";
export function MyScreen() {
return (
<HoverCard>
<HoverCardTrigger>
<Text className="text-sm font-medium text-primary underline">@aniui</Text>
</HoverCardTrigger>
<HoverCardContent>
<Text className="text-sm text-card-foreground">
Beautiful React Native components. Copy. Paste. Ship.
</Text>
</HoverCardContent>
</HoverCard>
);
}Installation#
npx @aniui/cli add hover-cardThis component requires @rn-primitives/hover-card, @rn-primitives/portal, and react-native-reanimated.
Usage#
app/index.tsx
import { Text } from "react-native";
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from "@/components/ui/hover-card";
export function MyScreen() {
return (
<HoverCard>
<HoverCardTrigger>
<Text className="text-sm font-medium text-primary underline">@aniui</Text>
</HoverCardTrigger>
<HoverCardContent>
<Text className="text-sm text-card-foreground">
Beautiful React Native components. Copy. Paste. Ship.
</Text>
</HoverCardContent>
</HoverCard>
);
}Components#
HoverCard is a compound component made up of several parts:
ComponentDescription
HoverCardRoot component that manages open/closed state. Supports both controlled and uncontrolled usage.
HoverCardTriggerThe pressable element that toggles the hover card. On mobile, activated by long-press.
HoverCardContentThe floating panel that displays the hover card content. Supports side, sideOffset, and align props.
Props#
HoverCard#
PropTypeDefault
openboolean-onOpenChange(open: boolean) => void-openDelaynumber-closeDelaynumber-childrenReact.ReactNoderequiredHoverCardContent#
PropTypeDefault
side"top" | "bottom" | "left" | "right""bottom"sideOffsetnumber8align"start" | "center" | "end""center"classNamestring-All sub-components also accept className and their respective React Native base props.
Accessibility#
- Uses
@rn-primitives/hover-cardfor proper trigger-relative positioning. - Collision detection prevents overflow off screen edges.
- Long-press trigger on mobile devices where hover is not available.
accessibilityRole="button"on trigger.- Requires
<PortalHost />at app root.
Source#
components/ui/hover-card.tsx
import React from "react";
import { View, Pressable } from "react-native";
import * as HoverCardPrimitive from "@rn-primitives/hover-card";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { cn } from "@/lib/utils";
export interface HoverCardProps {
children: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
openDelay?: number;
closeDelay?: number;
}
export function HoverCard({ children, open, onOpenChange, openDelay, closeDelay }: HoverCardProps) {
return (
<HoverCardPrimitive.Root
open={open}
onOpenChange={onOpenChange}
openDelay={openDelay}
closeDelay={closeDelay}
>
{children}
</HoverCardPrimitive.Root>
);
}
export interface HoverCardTriggerProps extends React.ComponentPropsWithoutRef<typeof Pressable> {
className?: string;
children?: React.ReactNode;
}
export function HoverCardTrigger({ className, children, ...props }: HoverCardTriggerProps) {
return (
<HoverCardPrimitive.Trigger asChild>
<Pressable
className={cn("min-h-12 min-w-12", className)}
accessible={true}
accessibilityRole="button"
{...props}
>
{children}
</Pressable>
</HoverCardPrimitive.Trigger>
);
}
export interface HoverCardContentProps extends React.ComponentPropsWithoutRef<typeof View> {
className?: string;
children?: React.ReactNode;
side?: "top" | "bottom" | "left" | "right";
sideOffset?: number;
align?: "start" | "center" | "end";
}
export function HoverCardContent({
className,
children,
side = "bottom",
sideOffset = 8,
align = "center",
...props
}: HoverCardContentProps) {
return (
<HoverCardPrimitive.Portal>
<HoverCardPrimitive.Overlay className="absolute inset-0" />
<HoverCardPrimitive.Content
side={side}
sideOffset={sideOffset}
align={align}
avoidCollisions
>
<Animated.View entering={FadeIn.duration(150)} exiting={FadeOut.duration(100)}>
<View
className={cn("w-64 rounded-lg border border-border bg-card p-4 shadow-lg", className)}
{...props}
>
{children}
</View>
</Animated.View>
</HoverCardPrimitive.Content>
</HoverCardPrimitive.Portal>
);
}