PreviewCard

A popup that appears when a link is hovered, showing a preview for sighted users.

Anatomy

Import and assemble the component:

1import { PreviewCard } from "@raystack/apsara";
2
3<PreviewCard>
4 <PreviewCard.Trigger href="/some-url">Hover me</PreviewCard.Trigger>
5 <PreviewCard.Content>
6 Preview content here
7 </PreviewCard.Content>
8</PreviewCard>

Usage

A preview card opens on hover after a short delay. The props below cover how it looks and where it lands.

With arrow

Display an arrow pointing to the trigger element.

1<PreviewCard>
2 <PreviewCard.Trigger href="#">Hover to preview</PreviewCard.Trigger>
3 <PreviewCard.Content showArrow>
4 <Flex direction="column" gap={3}>
5 <div
6 style={{
7 width: 200,
8 height: 120,
9 borderRadius: "var(--rs-radius-2)",
10 backgroundColor: "var(--rs-color-background-accent-primary)",
11 }}
12 />
13 <Text size="small">Preview content with an arrow indicator</Text>
14 </Flex>
15 </PreviewCard.Content>

Content transitions

When multiple triggers share one card via createHandle, wrap content in Viewport to animate transitions between them. Content slides in from the direction of the newly hovered trigger.

1(function ContentTransitionsExample() {
2 const handle = PreviewCard.createHandle();
3 const pages = {
4 "page-a": {
5 color: "var(--rs-color-background-accent-primary)",
6 title: "Getting Started",
7 description: "Learn the basics of the design system.",
8 },
9 "page-b": {
10 color: "var(--rs-color-background-success-primary)",
11 title: "Components",
12 description:
13 "Explore the full component library with interactive examples and detailed API documentation.",
14 },
15 "page-c": {

Positioning

Control the position of the preview card relative to its trigger.

1<PreviewCard>
2 <PreviewCard.Trigger href="#">Hover me</PreviewCard.Trigger>
3 <PreviewCard.Content side="top">
4 <Flex direction="column" gap={3}>
5 <div
6 style={{
7 width: 200,
8 height: 120,
9 borderRadius: "var(--rs-radius-2)",
10 backgroundColor: "var(--rs-color-background-accent-primary)",
11 }}
12 />
13 <Text size="small">Content appears above the trigger</Text>
14 </Flex>
15 </PreviewCard.Content>

Controlled

Pass open with onOpenChange to own the state. Use it when the card has to stay open while the reader interacts with it, rather than closing on pointer-out.

1(function ControlledPreviewCard() {
2 const [open, setOpen] = React.useState(false);
3
4 return (
5 <Flex direction="column" align="center" gap={5}>
6 <PreviewCard open={open} onOpenChange={setOpen}>
7 <PreviewCard.Trigger href="#">Apsara</PreviewCard.Trigger>
8 <PreviewCard.Content>
9 <Text size="small">A React component library.</Text>
10 </PreviewCard.Content>
11 </PreviewCard>
12 <Button size="small" variant="outline" onClick={() => setOpen((o) => !o)}>
13 Toggle from outside
14 </Button>
15 </Flex>

API Reference

A trigger and its content, with Viewport for cards whose contents change size.

Root

Groups all parts of the preview card.

Prop

Type

Trigger

A link element that opens the preview card on hover. Renders an <a> tag.

Prop

Type

Content

Renders the preview card content panel, wrapping Portal, Positioner, and Popup internally.

Prop

Type

Viewport

Optional wrapper for animating content transitions when multiple triggers share one card via createHandle. Provides direction-aware slide animations.

Prop

Type

Slots

Every rendered part carries a stable data-slot attribute for styling and testing:

SlotElement
preview-card-positionerPositioner that places the popup
preview-card-contentThe preview card popup
preview-card-viewportThe PreviewCard.Viewport wrapper
preview-card-arrowThe arrow (when showArrow)

Accessibility

  • Opens on hover with configurable delay (default 600ms)
  • Closes when the pointer leaves the trigger or content area
  • Content is accessible to screen readers when open
  • Trigger renders as a semantic <a> element
  • Respects motion preferences: preview card open/close motion is enabled only when prefers-reduced-motion: no-preference