[ BUG ]HoverCard doesn't work with modal. The card shows under the modal instead of above it.
Describe the bug The HoverCard component doesn't render correctly. Ideally, the card should have showed up above the modal, but it was hidden under it. It seems modal will be the top one layer regardless of in where you put the PortalHost.
Steps to reproduce the behavior:
- npx @react-native-reusables/cli@latest init
- npx @react-native-reusables/cli@latest add hover-card
- Add Modal and a nested Hovercard into index.tsx
import { Button } from '@/components/ui/button';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { PortalHost } from '@rn-primitives/portal';
import { Link, Stack } from 'expo-router';
import { MoonStarIcon, StarIcon, SunIcon } from 'lucide-react-native';
import { useColorScheme } from 'nativewind';
import * as React from 'react';
import { Image, type ImageStyle, Modal, View } from 'react-native';
const LOGO = {
light: require('@/assets/images/react-native-reusables-light.png'),
dark: require('@/assets/images/react-native-reusables-dark.png'),
};
const SCREEN_OPTIONS = {
title: 'React Native Reusables',
headerTransparent: true,
headerRight: () => <ThemeToggle />,
};
const IMAGE_STYLE: ImageStyle = {
height: 76,
width: 76,
};
export default function Screen() {
const { colorScheme } = useColorScheme();
return (
<>
<Stack.Screen options={SCREEN_OPTIONS} />
<Modal visible={true} transparent>
<View className="flex-1 bg-gray-200 opacity-50">
<HoverCard>
<HoverCardTrigger>
<Text>Hover</Text>
</HoverCardTrigger>
<HoverCardContent>
<Text>The React Framework – created and maintained by @vercel.</Text>
</HoverCardContent>
</HoverCard>
</View>
</Modal>
<View className="flex-1 items-center justify-center gap-8 p-4">
<Image source={LOGO[colorScheme ?? 'light']} style={IMAGE_STYLE} resizeMode="contain" />
<View className="gap-2 p-4">
<Text className="ios:text-foreground font-mono text-sm text-muted-foreground">
1. Edit <Text variant="code">app/index.tsx</Text> to get started.
</Text>
<Text className="ios:text-foreground font-mono text-sm text-muted-foreground">
2. Save to see your changes instantly.
</Text>
</View>
<View className="flex-row gap-2">
<Link href="https://reactnativereusables.com" asChild>
<Button>
<Text>Browse the Docs</Text>
</Button>
</Link>
<Link href="https://github.com/founded-labs/react-native-reusables" asChild>
<Button variant="ghost">
<Text>Star the Repo</Text>
<Icon as={StarIcon} />
</Button>
</Link>
</View>
</View>
</>
);
}
const THEME_ICONS = {
light: SunIcon,
dark: MoonStarIcon,
};
function ThemeToggle() {
const { colorScheme, toggleColorScheme } = useColorScheme();
return (
<Button
onPressIn={toggleColorScheme}
size="icon"
variant="ghost"
className="ios:size-9 rounded-full web:mx-4">
<Icon as={THEME_ICONS[colorScheme ?? 'light']} className="size-5" />
</Button>
);
}
- See error
Expected behavior the card should have showed up above the modal, but it was hidden under it. It seems modal will be the top one layer regardless of in where you put the PortalHost. You can see from the screenshot below. The card is cover by the gray modal layer which I gave opacity-50 to it.
Screenshots
- Type: Browser
- OS: Web
- Browser chrome
- Dependencies:
{
"name": "my-app",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"dev": "expo start -c",
"android": "expo start -c --android",
"ios": "expo start -c --ios",
"web": "expo start -c --web",
"clean": "rm -rf .expo node_modules"
},
"dependencies": {
"@react-navigation/native": "^7.0.0",
"@rn-primitives/hover-card": "^1.2.0",
"@rn-primitives/portal": "~1.3.0",
"@rn-primitives/slot": "^1.2.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"expo": "^54.0.0",
"expo-linking": "~8.0.8",
"expo-router": "~6.0.10",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8",
"expo-system-ui": "~6.0.7",
"lucide-react-native": "^0.545.0",
"nativewind": "^4.2.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.4",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-web": "^0.21.0",
"react-native-worklets": "0.5.1",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.14",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@types/react": "~19.1.10",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"typescript": "~5.9.2"
},
"private": true
}
Has anybody found a workaround for this?
Hey @Coolister-Ye, you can add a PortalHost as a child to the Modal, give it a name (e.g. "modal-portal"), then in the components/ui/hover-card.tsx, pass the same host name to the HoverCardPrimitive.Portal
index.tsx
</HoverCard>
</View>
+ <PortalHost name="modal-portal" />
</Modal>
components/ui/hover-card.tsx
+ <HoverCardPrimitive.Portal hostName="modal-portal">
- <HoverCardPrimitive.Portal>