Overlays & menus
Peek Card
Origin-anchored contextual preview card with edge-aware collision detection
detent-peek-card
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-overlays
- Group
- Overlays & menus
8.3 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
hoverrecorded state; no setterpressedrecorded state; no setterloadingrecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/peek-card/define.js';
// or register it yourself
import { DetentPeekCard } from '/components/peek-card/peek-card.js';
Attributes
delayis-loadingplacement
Properties
datarenderContent
Slots
default
Events
No events are recorded for this component.
Example
Runnable example: site/examples/peek-card.ts. The snippet below is generated from the catalogued attributes.
<detent-peek-card
delay="200"
is-loading
placement="auto"
></detent-peek-card>
Property-only values
const element = document.querySelector('detent-peek-card');
if (element !== null) {
// data: PeekCardData
element.data = undefined;
}
Source
The pinned upstream source for this row:
src/components/ui/PeekCard.tsxsha256 324ab3e00bfe…
Native files
The native module and its artifacts:
src/components/peek-card/peek-card.tssrc/components/peek-card/styles.csssrc/components/peek-card/define.tsdocs/components/peek-card.mdsite/examples/peek-card.tstests/browser/peek-card.spec.tsevidence/components/peek-card.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
delay | delay | number | 200 | Kebab-case attribute; camelCase property. |
is-loading | isLoading | boolean | false | Presence semantics: present means true, absent means false. |
placement | placement | 'top' | 'bottom' | 'auto' | 'auto' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
data | PeekCardData | [...] | Property-only: assign from script; not accepted as an attribute. |
renderContent | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
Events
No custom events are recorded for this component.
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- children becomes the default slot, className becomes parts/custom properties. The trigger is never wrapped in a cloned React element, so its activation and listeners are untouched (PC-11.2/11.6).
- data stays plain data and renderContent returns a DOM node. Upstream accepted ReactNode; a string is rendered as text (PC-11.1) and consumer markup must arrive as a node from the callback. actions remains accepted for shape parity but is not rendered (upstream never rendered it either).
- A manual popover replaces the absolute div. The card is in the top layer, so ancestor overflow/transform/z-index cannot clip or misplace it (PC-9.8).
- Placement is clamped. Upstream centred the card with left-1/2 and could overflow the viewport horizontally; this port clamps on both axes and keeps the upstream 220 px auto-flip rule.
- The pointer can reach the card. Upstream's wrapper onMouseLeave closed the card the moment the pointer left the trigger, so its copy button was unreachable by mouse; here a 120 ms grace period closes it only if the pointer does not enter the card, and leaving the card closes it.
- Copy feedback is announced once. Upstream flipped a copied flag with no announcement; the native card writes one message into a single role="status" node, reports failures instead of silently ignoring them, and clears it on close.
- The entry spring is the shared helper with the pinned springSnappy values; reduced motion writes the settled state with no frames (PC-10.2/10.5). The Framer Motion exit animation is dropped (the card hides immediately).
- Palette from tokens. Upstream hard-coded a dark card (#0E0E0E, #1F1F1F, #FAFAFA, #6B6B6B, emerald); this port reads --detent-color-* so light and dark are both legible (PC-9.5).
- lucide-react's copy/check icons are local decorative inline SVGs (PC-11.5).
- The 2 s copy reset timer is cancelled on close/disconnect; upstream's setTimeout was never cleared.
What it does
An origin-anchored preview card: hovering, focusing or tapping a trigger reveals a rich record (payment, invoice, profile) in a top-layer popover that emerges from the trigger, flips when there is no room, and stays inside the viewport.
Upstream behaviour
- Origin-anchored emergence animation feeling connected to source elements
- Automatic edge collision detection preventing viewport bounding overflow
- Rich preset layout for transactions, user profiles, invoices, and metrics
- Mobile tap toggle and desktop hover/focus dual interaction model
- Integrated copy actions and status indicators with reduced-motion support
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- children becomes the default slot, className becomes parts/custom properties. The trigger is never wrapped in a cloned React element, so its activation and listeners are untouched (PC-11.2/11.6).
- data stays plain data and renderContent returns a DOM node. Upstream accepted ReactNode; a string is rendered as text (PC-11.1) and consumer markup must arrive as a node from the callback. actions remains accepted for shape parity but is not rendered (upstream never rendered it either).
- A manual popover replaces the absolute div. The card is in the top layer, so ancestor overflow/transform/z-index cannot clip or misplace it (PC-9.8).
- Placement is clamped. Upstream centred the card with left-1/2 and could overflow the viewport horizontally; this port clamps on both axes and keeps the upstream 220 px auto-flip rule.
- The pointer can reach the card. Upstream's wrapper onMouseLeave closed the card the moment the pointer left the trigger, so its copy button was unreachable by mouse; here a 120 ms grace period closes it only if the pointer does not enter the card, and leaving the card closes it.
- Copy feedback is announced once. Upstream flipped a copied flag with no announcement; the native card writes one message into a single role="status" node, reports failures instead of silently ignoring them, and clears it on close.
- The entry spring is the shared helper with the pinned springSnappy values; reduced motion writes the settled state with no frames (PC-10.2/10.5). The Framer Motion exit animation is dropped (the card hides immediately).
- Palette from tokens. Upstream hard-coded a dark card (#0E0E0E, #1F1F1F, #FAFAFA, #6B6B6B, emerald); this port reads --detent-color-* so light and dark are both legible (PC-9.5).
- lucide-react's copy/check icons are local decorative inline SVGs (PC-11.5).
- The 2 s copy reset timer is cancelled on close/disconnect; upstream's setTimeout was never cleared.