OverlaysReact
A link that shows a preview of its destination on hover or focus, without interrupting keyboard or screen reader navigation.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<PreviewCard.Root>
<PreviewCard.Trigger />
<PreviewCard.Portal>
<PreviewCard.Positioner>
<PreviewCard.Popup>
<PreviewCard.Arrow />
</PreviewCard.Popup>
</PreviewCard.Positioner>
</PreviewCard.Portal>
</PreviewCard.Root>
// Not in this example: PreviewCard.Backdrop, PreviewCard.ViewportExamples
Detached Triggers Simple
A trigger outside Root opens it through a handle.
Detached Triggers Controlled
open with triggerId lets several links share one preview.
Detached Triggers Full
Viewport animates the content when another link opens the same preview.
API reference
28 props across 8 parts, in React syntax. className, style, render are listed once, under Shared props.
ArrowPreviewCard.Arrow
Displays an element positioned against the preview card anchor. Renders a <div>.
| Data attribute | Description |
|---|---|
data-open | Present when the preview card is open. |
data-closed | Present when the preview card is closed. |
data-side | Indicates which side the popup is positioned relative to the trigger. |
data-align | Indicates how the popup is aligned relative to specified side. |
data-uncentered | Present when the preview card arrow is uncentered. |
BackdropPreviewCard.Backdrop
A presentational overlay displayed beneath the popup. Renders a <div>.
| Data attribute | Description |
|---|---|
data-open | Present when the preview card is open. |
data-closed | Present when the preview card is closed. |
data-starting-style | Present when the preview card begins animating in. |
data-ending-style | Present when the preview card is animating out. |
PopupPreviewCard.Popup
A container for the preview card contents. Renders a <div>.
| Data attribute | Description |
|---|---|
data-open | Present when the preview card is open. |
data-closed | Present when the preview card is closed. |
data-starting-style | Present when the preview card begins animating in. |
data-ending-style | Present when the preview card is animating out. |
data-side | Indicates which side the popup is positioned relative to the trigger. |
data-align | Indicates how the popup is aligned relative to specified side. |
PortalPreviewCard.Portal
A portal element that moves the popup to a different part of the DOM. By default, the portal element is appended to <body>. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
containerA parent element to render the portal element into. | HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null | — |
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden. | boolean | false |
PositionerPreviewCard.Positioner
Positions the popup against the trigger. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
disableAnchorTrackingWhether to disable the popup from tracking any layout shift of its positioning anchor. | boolean | false |
alignHow to align the popup relative to the specified side. | Align | 'center' |
alignOffsetAdditional offset along the alignment axis in pixels. | number | OffsetFunction | 0 |
sideWhich side of the anchor element to align the popup against. | Side | 'bottom' |
sideOffsetDistance between the anchor and the popup in pixels. | number | OffsetFunction | 0 |
arrowPaddingMinimum distance to maintain between the arrow and the edges of the popup. | number | 5 |
anchorAn element to position the popup against. | Element | VirtualElement | React.RefObject<Element | null> | (() => Element | VirtualElement | null) | null | — |
collisionAvoidanceDetermines how to handle collisions when positioning the popup. | CollisionAvoidance | — |
collisionBoundaryAn element or a rectangle that delimits the area that the popup is confined to. | Boundary | 'clipping-ancestors' |
collisionPaddingAdditional space to maintain from the edge of the collision boundary. | Padding | 5 |
stickyWhether to maintain the popup in the viewport after | boolean | false |
positionMethodDetermines which CSS position property to use. | "absolute" | "fixed" | 'absolute' |
| Data attribute | Description |
|---|---|
data-open | Present when the preview card is open. |
data-closed | Present when the preview card is closed. |
data-anchor-hidden | Present when the anchor is hidden. |
data-side | Indicates which side the popup is positioned relative to the trigger. |
data-align | Indicates how the popup is aligned relative to specified side. |
| CSS variable | Description |
|---|---|
--available-width | The available width between the trigger and the edge of the viewport. |
--available-height | The available height between the trigger and the edge of the viewport. |
--anchor-width | The anchor's width. |
--anchor-height | The anchor's height. |
--transform-origin | The coordinates that this element is anchored to. Used for animations and transitions. |
--positioner-width | The width of the preview card's positioner.
It is important to set width to this value when using CSS to animate size changes. |
--positioner-height | The height of the preview card's positioner.
It is important to set height to this value when using CSS to animate size changes. |
RootPreviewCard.Root
Groups all parts of the preview card. Doesn’t render its own element.
| Prop | Type | Default |
|---|---|---|
defaultOpenWhether the preview card is initially open. | boolean | false |
openWhether the preview card is currently open. | boolean | — |
onOpenChangeEvent handler called when the preview card is opened or closed. | ((open: boolean, eventDetails: PreviewCardRootChangeEventDetails) => void) | — |
actionsRefA ref to imperative actions. | React.RefObject<PreviewCardRootActions | null> | — |
defaultTriggerIdID of the trigger that the preview card is associated with. | string | null | — |
handleA handle to associate the preview card with a trigger. | PreviewCardHandle<Payload> | — |
onOpenChangeCompleteEvent handler called after any animations complete when the preview card is opened or closed. | ((open: boolean) => void) | — |
triggerIdID of the trigger that the preview card is associated with. | string | null | — |
childrenThe content of the preview card. | React.ReactNode | PayloadChildRenderFunction<Payload> | — |
TriggerPreviewCard.Trigger
A link that opens the preview card. Renders an <a>.
| Prop | Type | Default |
|---|---|---|
handleA handle to associate the trigger with a preview card. | PreviewCardHandle<Payload> | — |
payloadA payload to pass to the preview card when it is opened. | Payload | — |
delayHow long to wait before the preview card opens. | number | 600 |
closeDelayHow long to wait before closing the preview card. | number | 300 |
| Data attribute | Description |
|---|---|
data-popup-open | Present when the corresponding preview card is open. |
ViewportPreviewCard.Viewport
A viewport for displaying content transitions. This component is only required if one popup can be opened by multiple triggers, its content changes based on the trigger, and switching between them is animated. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
childrenThe content to render inside the transition container. | React.ReactNode | — |
| Data attribute | Description |
|---|---|
data-current | Applied to the direct child of the viewport when no transitions are present or the new content when it's entering. |
data-previous | Applied to the direct child of the viewport that contains the exiting content when transitions are present. |
data-activation-direction | Indicates the direction from which the popup was activated. This can be used to create directional animations based on how the popup was triggered. Contains space-separated values for both horizontal and vertical axes. |
data-transitioning | Indicates that the viewport is currently transitioning between old and new content. |
data-instant | Present if animations should be instant. |
| CSS variable | Description |
|---|---|
--popup-width | The width of the parent popup. This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered. It can be used to freeze the dimensions of the popup when animating between different content. |
--popup-height | The height of the parent popup. This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered. It can be used to freeze the dimensions of the popup when animating between different content. |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| Tab | Focusing the link opens the preview | Trigger |
| Escape | Closes the preview | Popup |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Trigger | renders an <a> and keeps the link's own semantics | the link is read and followed like any other |
| Popup | not linked to the trigger and not in the tab order | screen reader and keyboard users stay on the page and are not taken through the preview |
| Arrow | aria-hidden="true" | the decorative arrow is not read |
| Backdrop | role="presentation" | the overlay is not read |
Focus order
- Trigger is a tab stop; focus never moves into Popup.
- Moving focus away, or
Escape, closes the preview.