You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.9 KiB
4.9 KiB
LinkPreview
A floating preview card that appears on hover over a link. Supports configurable open/close delays, positioning via @floating-ui, safe pointer transitions, and dismissal.
Anatomy
<LinkPreview.Provider>
<LinkPreview.Trigger href="https://svelte.dev">Svelte</LinkPreview.Trigger>
<LinkPreview.Content>
<LinkPreview.Arrow />
<div>Preview content here</div>
</LinkPreview.Content>
</LinkPreview.Provider>
Parts
| Part | Element | Description |
|---|---|---|
Provider |
none | Root context. Manages open state, delays, and pointer tracking. |
Trigger |
<a> |
Link element. Opens preview on pointer hover. |
Content |
<div> |
Floating preview card. Positioned via @floating-ui. |
Arrow |
<svg> |
Optional arrow pointing toward the trigger. |
ARIA
| Part | Attribute | Value |
|---|---|---|
| Content | aria-hidden |
true |
Accessibility
LinkPreview is invisible to assistive technology by design. The floating
preview card exists for sighted-hover discovery only — it adds no
information a screen-reader user could not already reach through the
underlying <a> element. Emitting aria-hidden="true" on Content is a
deliberate choice:
- Duplicate content: the preview typically mirrors the destination page summary that a screen reader would announce after following the link. Re-reading it as a tooltip is noise, not help.
- Hover-only activation: APG explicitly recommends against exposing pure-hover widgets to assistive tech because keyboard / touch users never see them anyway — making them invisible everyone except mouse hover is the consistent experience.
- Focus stays on the trigger: the preview never steals focus, never traps Tab, and closes automatically on blur. There is nothing for a keyboard / AT user to miss.
If your LinkPreview contains content that is NOT in the destination page
(a custom CTA, a badge, a pricing hint), consider using Popover or
Tooltip instead — both are AT-visible.
Data Attributes
| Part | Attribute | Values |
|---|---|---|
| Trigger | data-link-preview-trigger |
Always present |
| Trigger | data-state |
open | closed |
| Content | data-link-preview-content |
Always present |
| Content | data-state |
open | closed |
| Content | data-side |
top | right | bottom | left |
| Content | data-align |
start | center | end |
| Content | data-starting-style |
Present during open animation |
| Content | data-ending-style |
Present during close animation |
| Arrow | data-link-preview-arrow |
Always present |
| Arrow | data-side |
top | right | bottom | left |
CSS Variables
| Variable | Part | Description |
|---|---|---|
--soma-floating-transform-origin |
Content | Transform origin for animations |
--soma-floating-available-width |
Content | Available viewport width |
--soma-floating-available-height |
Content | Available viewport height |
--soma-floating-anchor-width |
Content | Trigger element width |
--soma-floating-anchor-height |
Content | Trigger element height |
Keyboard
| Key | Action |
|---|---|
Escape |
Close the preview immediately (bypasses delay) |
Usage
Basic
<script>
import { LinkPreview } from '$soma/components';
</script>
<LinkPreview.Provider>
<LinkPreview.Trigger href="https://svelte.dev">Svelte</LinkPreview.Trigger>
<LinkPreview.Content>
<strong>Svelte</strong>
<p>Cybernetically enhanced web apps.</p>
</LinkPreview.Content>
</LinkPreview.Provider>
With arrow
<LinkPreview.Content>
<LinkPreview.Arrow />
<div>Preview content</div>
</LinkPreview.Content>
Custom delays
<LinkPreview.Provider openDelay={300} closeDelay={150}>
<!-- Opens faster, closes faster -->
</LinkPreview.Provider>
Custom positioning
<LinkPreview.Content side="bottom" align="center" sideOffset={12}>
<!-- Appears below the link, centered, with 12px gap -->
</LinkPreview.Content>
Disabled
<LinkPreview.Provider disabled>
<!-- Hover does nothing -->
</LinkPreview.Provider>