implement
Link Preview

Link Preview

A card about a link, revealed by resting the pointer on it.

The primitives are unstyled building blocks maintained by .

import { Div, P, Span } from "@implementjs/core";
import { CalendarDaysIcon } from "@implementjs/lucide";
import { Avatar, AvatarFallback, AvatarImage } from "@/lib/components/ui/avatar";
import {
	LinkPreview,
	LinkPreviewContent,
	LinkPreviewPortal,
	LinkPreviewTrigger,
} from "@/lib/components/ui/link-preview";

export default function LinkPreviewDemo() {
	return P(
		{ class: "max-w-sm text-sm text-muted-foreground" },
		"The primitives are unstyled building blocks maintained by ",
		LinkPreview(
			LinkPreviewTrigger(
				{ href: "https://github.com/ieedan", target: "_blank", rel: "noreferrer" },
				"@ieedan",
			),
			LinkPreviewPortal(
				LinkPreviewContent(
					Div(
						{ class: "flex gap-4" },
						Avatar(
							{ class: "size-12" },
							AvatarImage({ src: "https://github.com/ieedan.png", alt: "@ieedan" }),
							AvatarFallback("AB"),
						),
						Div(
							{ class: "space-y-1" },
							Div({ class: "text-sm font-semibold" }, "@ieedan"),
							P({ class: "text-sm" }, "Building implement — a signal-based UI framework."),
							Div(
								{ class: "flex items-center gap-2 pt-1" },
								CalendarDaysIcon({ "aria-hidden": true, class: "size-4 opacity-70" }),
								Span({ class: "text-xs text-muted-foreground" }, "Joined December 2021"),
							),
						),
					),
				),
			),
		),
		".",
	);
}

Installation

npx jsrepo add @implementjs/ui/link-preview

Nothing else comes with it — this one stands alone on @implementjs/core and @implementjs/primitives.

Usage

Pointer-only by design: a link preview is an enrichment, so it never opens on focus or on touch, and nothing inside it is reachable by keyboard that is not reachable another way. Put content in it, not controls.

The trigger is styled as an underlined link. The card is a 20rem popover panel that opens above the link by default.

import {
	LinkPreview,
	LinkPreviewContent,
	LinkPreviewTrigger,
} from "@/lib/components/ui/link-preview";

LinkPreview(
	LinkPreviewTrigger({ href: "https://github.com/ieedan" }, "@ieedan"),
	LinkPreviewContent(
		Div({ class: "flex gap-3" }, Avatar(AvatarFallback("AB")), Span("Aidan Bleser")),
	),
);

Timing and placement

openDelay and closeDelay are on the root; side, align, and offset on the content. The styled content defaults to top / center / 8 — a card that sits above the sentence rather than covering the words after it.

API Reference

Every prop the styling does not consume is forwarded to the Link Preview primitive, so the tables below are the whole surface — the behavior props and the styling ones together.

LinkPreview

The root. Owns whether the preview is open, the hover delays, and provides that to the parts inside it.

PropTypeDefaultDescription
openSignal<boolean> | booleanfalseThe open state. Pass a signal to control it from outside; a boolean seeds uncontrolled state.
preventScrollbooleantrueWhen true, the page behind cannot scroll while the preview is open. The panel can still scroll if you give it overflow.
disabledSignal<boolean> | booleanfalseWhile true the preview never opens. The link still navigates.
openDelaynumber700How long the pointer must rest on the link before the preview opens, in milliseconds.
closeDelaynumber300How long the preview stays up after the pointer leaves, in milliseconds. This is the window the pointer has to travel from the link to the preview.

LinkPreviewTrigger

The link the preview hangs off. Renders an anchor, so pass href and it navigates like any other link; hovering or keyboard-focusing it opens the preview. Styled as an underlined link. Renders a A; extra props are forwarded onto it.

Data attributeValue
[data-link-preview-trigger]Present
[data-state]"open" | "closed"

LinkPreviewContent

The preview card. Styled as a 20rem popover panel that fades and scales in, and hides itself when closed. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right""top"Preferred side of the link to place the panel.
align"start" | "center" | "end""center"How the panel aligns along the chosen side.
offsetnumber8Distance in pixels between the link and the panel.
Data attributeValue
[data-link-preview-content]Present
[data-state]"open" | "closed"
[data-side]"top" | "bottom" | "left" | "right"
[data-align]"start" | "center" | "end"
CSS variableDescription
--ip-link-preview-content-transform-originThe transform origin of the content element.
--ip-link-preview-content-available-widthThe available width of the content element.
--ip-link-preview-content-available-heightThe available height of the content element.
--ip-link-preview-anchor-widthThe width of the anchor element.
--ip-link-preview-anchor-heightThe height of the anchor element.

LinkPreviewPortal

Renders its children into another DOM parent so the panel escapes overflow and stacking. This is the core Portal helper; context still resolves from where the portal is declared.

PropTypeDefaultDescription
toHTMLElement | Readable<HTMLElement>document.bodyThe element to mount into. Also available as chained .To(target).
disabledboolean | Readable<boolean>falseMount in place instead of teleporting. Also available as chained .Disabled(value).