Detent web components
By Shane Logsdon

Overlays & menus

Peek Card

Implemented

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

  • hover recorded state; no setter
  • pressed recorded state; no setter
  • loading recorded state; no setter
  • reduced motion recorded 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

  • delay
  • is-loading
  • placement

Properties

  • data
  • renderContent

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.tsx sha256 324ab3e00bfe…

Native files

The native module and its artifacts:

  • src/components/peek-card/peek-card.ts
  • src/components/peek-card/styles.css
  • src/components/peek-card/define.ts
  • docs/components/peek-card.md
  • site/examples/peek-card.ts
  • tests/browser/peek-card.spec.ts
  • evidence/components/peek-card.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
delaydelaynumber200Kebab-case attribute; camelCase property.
is-loadingisLoadingbooleanfalsePresence semantics: present means true, absent means false.
placementplacement'top' | 'bottom' | 'auto''auto'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
dataPeekCardData[...]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

Slots
SlotNotes
defaultConsumer 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.
Previous: Payment Status Next: Photo Box

Docs

Components

Project