Preview Card
A non-modal rich preview that opens on hover or focus of an inline link.
Show codeHide code
Installation
import {
PreviewCard,
PreviewCardTrigger,
PreviewCardContent,
PreviewCardBody,
PreviewCardTitle,
PreviewCardDescription,
} from "@makeplane/propel/components/preview-card";Usage
import {
PreviewCard,
PreviewCardBody,
PreviewCardContent,
PreviewCardDescription,
PreviewCardTitle,
PreviewCardTrigger,
} from "@makeplane/propel/components/preview-card";
export default function BasicDemo() {
return (
<PreviewCard>
<PreviewCardTrigger href="https://plane.so">Plane</PreviewCardTrigger>
<PreviewCardContent side="top">
<PreviewCardBody>
<PreviewCardTitle>Plane</PreviewCardTitle>
<PreviewCardDescription>
Open-source project management for issues, sprints, and roadmaps.
</PreviewCardDescription>
</PreviewCardBody>
</PreviewCardContent>
</PreviewCard>
);
}Examples
With image
Show codeHide code
With properties
Show codeHide code
Controlled
A controlled card owns its own visibility through open / onOpenChange, and triggerId selects which trigger it anchors to — letting application code open the card programmatically.
Show codeHide code
Detached triggers
createPreviewCardHandle() links triggers outside the PreviewCard root to it via the handle prop; each trigger passes a payload the card renders through function-as-children, so one card serves several links.
Show codeHide code
API Reference
PreviewCard
The preview-card Root — Base UI's context/state provider (renders no element of its own). A
behavior-only role, so it lives in components (rules 1a, 2); the styled parts live in
elements/preview-card (and shared internal/ primitives) and are grafted onto Base UI behavior
here.
PreviewCardTrigger
The link that opens the preview card on hover or focus. Renders an <a> by default; pass
render to project the trigger onto your own link. Maps 1:1 to PreviewCard.Trigger.
PreviewCardContent
Convenience that composes the anchored preview card boilerplate — Base UI portal, positioner, and
popup grafted onto Propel's styled surface — so a consumer only writes the trigger and the card
body. Pass side/sideOffset/align through to the positioner.
No backdrop: unlike Dialog/AlertDialog/Drawer, a preview card is a non-modal, hover-triggered
rich tooltip (it opens on hover/focus and closes on pointer-leave/blur, with no focus trap) —
dimming the page behind a hover preview reads as a modal takeover it isn't. Base UI ships a
PreviewCard.Backdrop primitive, but it is purely optional/decorative for apps that want one; it
has none of Dialog.Backdrop's click-outside-to-dismiss semantics, so omitting it costs no
behavior.
PreviewCardBody
The text content area of the card — typically holds a PreviewCardTitle and
PreviewCardDescription stacked in a column. Owns the padding so a full-bleed PreviewCardImage
can sit edge-to-edge above it; both the column layout and the padding are "always the same" per
the design spec.
PreviewCardTitle
The card's primary heading — text-body-sm-medium per the design spec. Truncates to a single
line instead of wrapping (Figma titles are nowrap on the 296px card). Diverges from the shared
internal/overlay-title "md" step (semibold) — that step has no current consumer (this family
used to adopt it) and is lighter medium here per Figma, so it keeps its own recipe (rule 4a:
divergent styling stays per-family). When children is a string, bakes a native title so hover
recovers the full text past the truncation; pass title explicitly to override (or title="" to
suppress).
PreviewCardDescription
Supporting description text beneath the title — the shared internal/overlay-description recipe
at the preview card's md size (13px secondary text). The size is fixed here (a components
default, rule 13).
PreviewCardImage
The thumbnail image shown inside the popup. Bakes in w-full, overflow-hidden, and object-cover
so the thumbnail always spans the card, clips, and fills its box — these are "always the same"
per the design spec. Height is set by the consumer's layout (native height, or the image's
intrinsic aspect ratio at full width).
PreviewCardEyebrow
Lays an optional leading node beside text on one row. The slot is unstyled flex — pass an icon, a
16×16 filled type indicator (Figma's issue frame), or any other leading glyph. Stories use the
shared internal/Icon at tint="secondary" size="md" as a stand-in. Issue cards put a
PreviewCardEyebrowLabel identifier (an issue key) here ABOVE a separate PreviewCardTitle.
Cycle / module / release / intake cards put PreviewCardTitle in this row and skip
PreviewCardEyebrowLabel — those canvases have no muted identifier. Compose it only when the
card has a leading glyph; a card without one renders PreviewCardTitle directly inside
PreviewCardBody.
PreviewCardEyebrowLabel
The single-line identifier text inside a PreviewCardEyebrow (an issue key, …). Muted one step
further than the description and truncates instead of wrapping. Not the card's heading — cycle /
module / release / intake names belong in PreviewCardTitle (optionally inside the eyebrow,
beside the icon). When children is a string, bakes a native title so hover recovers the full
text past the truncation; pass title explicitly to override (or title="" to suppress).
PreviewCardPropertyGroup
The row of property chips (status, priority, assignee, labels, …) shown beneath the description.
A bare flex-wrap row — the chips are the consumer's own components (Pill, Avatar, Badge,
…); this part only supplies the row layout. Never aria-hidden: its content is meaningful.
PreviewCardMeta
The muted footer caption closing out the card's text content (a source domain, a relative timestamp, …).