AniUI

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-card

This 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
HoverCard

Root component that manages open/closed state. Supports both controlled and uncontrolled usage.

HoverCardTrigger

The pressable element that toggles the hover card. On mobile, activated by long-press.

HoverCardContent

The floating panel that displays the hover card content. Supports side, sideOffset, and align props.

Props#

HoverCard#

PropTypeDefault
open
boolean
-
onOpenChange
(open: boolean) => void
-
openDelay
number
-
closeDelay
number
-
children
React.ReactNode
required

HoverCardContent#

PropTypeDefault
side
"top" | "bottom" | "left" | "right"
"bottom"
sideOffset
number
8
align
"start" | "center" | "end"
"center"
className
string
-

All sub-components also accept className and their respective React Native base props.

Accessibility#

  • Uses @rn-primitives/hover-card for 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>
  );
}