Radix homepageRadix Homepage
PrimitivesAlpha

Hover Card

For sighted users to preview content available behind a link.

import React from 'react';
import { styled, keyframes } from '@stitches/react';
import { mauve } from '@radix-ui/colors';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const StyledContent = styled(HoverCardPrimitive.Content, {
borderRadius: 6,
padding: 20,
width: 300,
backgroundColor: 'white',
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
},
},
});
const StyledArrow = styled(HoverCardPrimitive.Arrow, {
fill: 'white',
});
// Exports
export const HoverCard = HoverCardPrimitive.Root;
export const HoverCardTrigger = HoverCardPrimitive.Trigger;
export const HoverCardContent = StyledContent;
export const HoverCardArrow = StyledArrow;
// Your app...
const Flex = styled('div', { display: 'flex' });
const ImageTrigger = styled('a', {
all: 'unset',
cursor: 'pointer',
borderRadius: '100%',
display: 'inline-block',
'&:focus': { boxShadow: `0 0 0 2px white` },
});
const Img = styled('img', {
display: 'block',
borderRadius: '100%',
variants: {
size: {
normal: { width: 45, height: 45 },
large: { width: 60, height: 60 },
},
},
defaultVariants: {
size: 'normal',
},
});
const Text = styled('div', {
margin: 0,
color: mauve.mauve12,
fontSize: 15,
lineHeight: 1.5,
variants: {
faded: {
true: { color: mauve.mauve10 },
},
bold: {
true: { fontWeight: 500 },
},
},
});
const HoverCardDemo = () => (
<HoverCard>
<HoverCardTrigger as={ImageTrigger} href="https://twitter.com/radix_ui" target="_blank" rel="noreferrer noopener" >
<Img src="https://pbs.twimg.com/profile_images/1337055608613253126/r_eiMp2H_400x400.png" />
</HoverCardTrigger>
<HoverCardContent sideOffset={5}>
<Flex css={{ flexDirection: 'column', gap: 7 }}>
<Img size="large" src="https://pbs.twimg.com/profile_images/1337055608613253126/r_eiMp2H_400x400.png" />
<Flex css={{ flexDirection: 'column', gap: 15 }}>
<Text>
<Text bold>Radix</Text>
<Text faded>@radix_ui</Text>
</Text>
<Text>
Components, icons, colors, and templates for building high-quality, accessible UI. Free
and open-source.
</Text>
<Flex css={{ gap: 15 }}>
<Flex css={{ gap: 5 }}>
<Text bold>0</Text> <Text faded>Following</Text>
</Flex>
<Flex css={{ gap: 5 }}>
<Text bold>2,900</Text> <Text faded>Followers</Text>
</Flex>
</Flex>
</Flex>
</Flex>
<HoverCardArrow />
</HoverCardContent>
</HoverCard>
);
export default HoverCardDemo;

Features

  • Can be controlled or uncontrolled.
  • Customize side, alignment, offsets, collision handling.
  • Optionally render a pointing arrow.
  • Supports custom open and close delays.
  • Opens on hover only.
  • Ignored by screen readers.

Install the component from your command line.

npm install @radix-ui/react-hover-card

Import all parts and piece them together.

import * as HoverCard from '@radix-ui/react-hover-card';
export default () => (
<HoverCard.Root>
<HoverCard.Trigger />
<HoverCard.Content>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Root>
);

Contains all the parts of a hover card.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value
openDelaynumber700
closeDelaynumber300

The link that opens the hover card when hovered.

PropTypeDefault
asenuma

The component that pops out when the hover card is open.

PropTypeDefault
asenumdiv
forceMountbooleanNo default value
portalledbooleantrue
sideenum"bottom"
sideOffsetnumber0
alignenum"center"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionToleranceboolean0

An optional arrow element to render alongside the hover card. This can be used to help visually link the trigger with the HoverCard.Content. Must be rendered inside HoverCard.Content.

PropTypeDefault
asenumsvg
widthnumber10
heightnumber5
offsetnumberNo default value

Use the openDelay prop to control the time it takes for the hover card to open.

import * as HoverCard from '@radix-ui/react-hover-card';
export default () => (
<HoverCard.Root delayDuration={0}>
<HoverCard.Trigger></HoverCard.Trigger>
<HoverCard.Content></HoverCard.Content>
</HoverCard.Root>
);

We expose a CSS custom property --radix-hover-card-content-transform-origin. Use it to animate the content from its computed origin based on side, sideOffset, align, alignOffset and any collisions.

import { styled, keyframes } from '@stitches/react';
import * as HoverCard from '@radix-ui/react-hover-card';
const scaleIn = keyframes({
'0%': { opacity: 0, transform: 'scale(0)' },
'100%': { opacity: 1, transform: 'scale(1)' },
});
const HoverCardContent = styled(HoverCard.Content, {
transformOrigin: 'var(--radix-hover-card-content-transform-origin)',
animation: `${scaleIn} 0.5s ease-out`,
});
export default () => (
<HoverCard.Root>
<HoverCard.Trigger></HoverCard.Trigger>
<HoverCardContent></HoverCardContent>
</HoverCard.Root>
);

We expose data-side and data-align attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.

import { styled, keyframes } from '@stitches/react';
import * as HoverCard from '@radix-ui/react-hover-card';
const slideDown = keyframes({
'0%': { opacity: 0, transform: 'translateY(-10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideUp = keyframes({
'0%': { opacity: 0, transform: 'translateY(10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const HoverCardContent = styled(HoverCard.Content, {
'&[data-side="top"]': { animationName: slideUp },
'&[data-side="bottom"]': { animationName: slideDown },
animationDuration: '0.6s',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
});
export default () => (
<HoverCard.Root>
<HoverCard.Trigger></HoverCard.Trigger>
<HoverCardContent></HoverCardContent>
</HoverCard.Root>
);

The hover card is intended for mouse users only so will not respond to keyboard navigation.