Components
Preview Card
A card that previews a link's destination while the pointer or keyboard focus rests on the link, after Base UI's PreviewCard. The trigger is the link itself, which works as a link without JavaScript; the card is a manual popover placed with Popover's placement and opened with its hover timing by a 3.4 kB script. It is hoverable, persistent and dismissible (WCAG 1.4.13). bind:open keeps a signal in sync.
Lumiose components update through signals.
Values that update whatever reads them.
import { PreviewCard } from "@lumiose/ui"; // or "@lumiose/ui/preview-card"
import "@lumiose/ui/preview-card.css"; // places the card next to its link
import "@lumiose/ui/themes/default.css"; // optional: the default theme#Anatomy
<PreviewCard.Root>
<p>
… <PreviewCard.Trigger href="…" /> …
</p>
<PreviewCard.Popup />
</PreviewCard.Root>| Part | Renders |
|---|---|
Root |
No element. Holds the ids the other parts share. |
Trigger |
<a href data-preview-card="…">, with an anchor-name in its style; data-popup-open while it is open |
Popup |
<div popover="manual" data-side data-align>, placed next to the link |
The card is a <div>: put it after the paragraph that holds the link, not inside it (a <p>
cannot hold a <div>), with the Root around both.
#API
#PreviewCard.Root
| Prop | Type | Default | |
|---|---|---|---|
open |
boolean |
Opens or closes the card when it changes, in the browser. | |
onOpenChange |
(open: boolean) => void |
Called after the card opens or closes, whatever did it. | |
bind:open |
Signal<boolean> |
Stands in for open and onOpenChange. |
#PreviewCard.Trigger
| Prop | Type | Default | |
|---|---|---|---|
delay |
number |
600 |
How long the pointer or focus rests on the link before the card opens, in ms. |
closeDelay |
number |
300 |
How long the card stays after the pointer leaves the link and the card, in ms. |
Every other <a> prop passes through: href, target, class, aria-*.
#PreviewCard.Popup
side, align, sideOffset and alignOffset, as on Popover.Popup;
every other <div> prop passes through.
Differences from Base UI's PreviewCard. No Portal, Positioner or Viewport: the top
layer and CSS anchor positioning replace them, and the placement props are on Popup.
- No
Backdrop: the open card is in the top layer, so.lu-preview-card::backdropstyles the layer under it, without an element. - No
Arrow, as in Popover and Tooltip: anchor positioning gives no arrow offset when the card shifts to fit. A pointer that followsdata-sideis a::beforeof your CSS. - No detached triggers (
handle,payload) oractionsRef: a card's content is markup rendered with it, so each link has its Root and its card, in server HTML too. In an island, a signal per link does what a payload does.
#Example
import { PreviewCard } from "@lumiose/ui";
export function SignalsLink() {
return (
<PreviewCard.Root>
<p>
Lumiose components update through{" "}
<PreviewCard.Trigger href="/docs/concepts/signals/">signals</PreviewCard.Trigger>.
</p>
<PreviewCard.Popup class="lu-preview-card">
<strong>Signals</strong>
<p>Values that update whatever reads them.</p>
</PreviewCard.Popup>
</PreviewCard.Root>
);
}A card is a visual enhancement, as Base UI says: what it shows should also be on the linked page.
#Styling
With the default theme, put lu-preview-card on Popup: a surface like a popover's, 8px from
the link, with a fade (none where motion is reduced).
With your own CSS, style the card through class, :popover-open, [data-side] and
[data-align], and the link through [data-popup-open]. --lu-side-offset and
--lu-align-offset set the offsets.
@lumiose/ui/preview-card.css is the placement Popover and Tooltip share (placement.css).
#Keyboard and focus
| Key | Does |
|---|---|
| Tab | Onto the link: the card opens after the delay, focus stays on the link. Again: into the open card, then on. |
| Escape | Closes the card without moving the pointer; from inside the card, focus goes back to the link. |
| Enter | Follows the link. |
- Hover opens the card after the delay; it stays while the pointer is on the link or the card (it may cross the gap between them) and closes after the close delay once it left both.
- Focus opens it too; it closes when focus leaves the link and the card. The open card follows its link in the focus order (the link is its invoker).
- Touch follows the link: no card opens on a tap.
- A press outside closes it, as Escape does (the dismissal model of Popover and Menu).
#Without JavaScript, and the client script
The link is a plain link and the card never opens. A page that renders a PreviewCard loads its
script once, with the hover timing, dismissal and placement modules it shares with Popover: 3.4 kB
(brotli) without the Lumiose runtime; an island loads the same modules. It runs hover and focus with
their delays, dismissal, data-popup-open, and the placement in browsers without CSS anchor
positioning.
#Browser support
Popover and CSS anchor positioning as in Popover: the script places the card where anchor positioning is missing.