Beta, expect API change, and bugs. Hit one? Tell us.

Skip to content

Preview Card

A link preview opened on hover.

The rings of Saturn are made almost entirely of ice.

<script setup lang="ts">
import { PreviewCard } from '@shardsui/vue/preview-card'
</script>

<template>
  <PreviewCard.Root>
    <p class="m-0 text-sm/6 text-balance text-gray-900">
      The rings of
      <PreviewCard.Trigger
        class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
        href="https://en.wikipedia.org/wiki/Saturn"
      >
        Saturn
      </PreviewCard.Trigger>
      are made almost entirely of ice.
    </p>

    <PreviewCard.Portal>
      <PreviewCard.Positioner :side-offset="8">
        <PreviewCard.Popup
          class="box-border h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg bg-gray-50 shadow-lg outline-1 outline-gray-200 transition-[scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <PreviewCard.Arrow
            class="flex data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
              <path
                d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
                class="fill-gray-50"
              />
              <path
                d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
                class="fill-gray-200"
              />
            </svg>
          </PreviewCard.Arrow>
          <div class="box-border flex w-56 flex-col items-center gap-2 p-2">
            <div
              class="size-36 rounded-full"
              style="
                background: radial-gradient(
                  circle at 32% 28%,
                  #f8efcd 0%,
                  #e3ce85 45%,
                  #a8934a 100%
                );
              "
              aria-hidden="true"
            ></div>
            <p class="m-0 self-stretch text-sm/5 text-pretty text-gray-900">
              <strong>Saturn</strong> is the second largest planet, a pale gas giant so light it
              would float, wrapped in bright rings of ice.
            </p>
          </div>
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

Anatomy

<script setup>
import { PreviewCard } from '@shardsui/vue/preview-card'
</script>

<template>
  <PreviewCard.Root>
    <PreviewCard.Trigger />
    <PreviewCard.Portal>
      <PreviewCard.Backdrop />
      <PreviewCard.Positioner>
        <PreviewCard.Popup>
          <PreviewCard.Arrow />
          <PreviewCard.Viewport />
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

Usage guidelines

  • Popup content should reflect the link destination: avoid placing unique or essential information in the popup unless it is also available on the linked page. Preview cards only help pointer and keyboard users; they are not accessible to touch or screen reader users.

Examples

Detached triggers

The trigger normally nests inside <PreviewCard.Root>. When the link and its card can't sit together in the markup — a link running inline in a paragraph, say — render <PreviewCard.Trigger> wherever the link belongs and tie it back to the root with a shared handle from PreviewCard.createHandle().

The handle's imperative methods, open() and close(), need a <PreviewCard.Root> using the same handle to be mounted. Calls made before a root mounts or after it unmounts are ignored, not queued. Each root starts from fresh state when it mounts.

<script setup>
const demoPreviewCard = PreviewCard.createHandle()
</script>

<template>
  <!-- [!code word::handle="demoPreviewCard"] -->
  <PreviewCard.Trigger :handle="demoPreviewCard" href="#">Link</PreviewCard.Trigger>

  <PreviewCard.Root :handle="demoPreviewCard">...</PreviewCard.Root>
</template>

The rings of Saturn are made almost entirely of ice.

<script setup lang="ts">
import { PreviewCard } from '@shardsui/vue/preview-card'

const demoPreviewCard = PreviewCard.createHandle()
</script>

<template>
  <p class="m-0 text-sm/6 text-balance text-gray-900">
    The rings of
    <PreviewCard.Trigger
      class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
      :handle="demoPreviewCard"
      href="https://en.wikipedia.org/wiki/Saturn"
    >
      Saturn
    </PreviewCard.Trigger>
    are made almost entirely of ice.
  </p>

  <PreviewCard.Root :handle="demoPreviewCard">
    <PreviewCard.Portal>
      <PreviewCard.Positioner :side-offset="8">
        <PreviewCard.Popup
          class="box-border h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg bg-gray-50 shadow-lg outline-1 outline-gray-200 transition-[scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <PreviewCard.Arrow
            class="flex data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
              <path
                d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
                class="fill-gray-50"
              />
              <path
                d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
                class="fill-gray-200"
              />
            </svg>
          </PreviewCard.Arrow>
          <div class="box-border flex w-56 flex-col items-center gap-2 p-2">
            <div
              class="size-36 rounded-full"
              style="
                background: radial-gradient(
                  circle at 32% 28%,
                  #f8efcd 0%,
                  #e3ce85 45%,
                  #a8934a 100%
                );
              "
              aria-hidden="true"
            ></div>
            <p class="m-0 self-stretch text-sm/5 text-pretty text-gray-900">
              <strong>Saturn</strong> is the second largest planet, a pale gas giant so light it
              would float, wrapped in bright rings of ice.
            </p>
          </div>
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

Multiple triggers

One preview card can serve many links: nest several <PreviewCard.Trigger> elements in a single <PreviewCard.Root>, or point any number of detached triggers at the same handle.

<template>
  <PreviewCard.Root>
    <PreviewCard.Trigger href="#">Trigger 1</PreviewCard.Trigger>
    <PreviewCard.Trigger href="#">Trigger 2</PreviewCard.Trigger>
    ...
  </PreviewCard.Root>
</template>
<script setup>
const demoPreviewCard = PreviewCard.createHandle()
</script>

<template>
  <PreviewCard.Trigger :handle="demoPreviewCard" href="#">Trigger 1</PreviewCard.Trigger>

  <PreviewCard.Trigger :handle="demoPreviewCard" href="#">Trigger 2</PreviewCard.Trigger>

  <PreviewCard.Root :handle="demoPreviewCard">...</PreviewCard.Root>
</template>

Each trigger can feed the card its own data through the payload prop, so one card shows a different preview per link. Read it from the default slot on <PreviewCard.Root>. Pass a type argument to PreviewCard.createHandle() to type the payload:

<script setup lang="ts">
const demoPreviewCard = PreviewCard.createHandle<{ title: string }>()
</script>

<template>
  <PreviewCard.Trigger :handle="demoPreviewCard" :payload="{ title: 'Trigger 1' }" href="#">
    Trigger 1
  </PreviewCard.Trigger>

  <PreviewCard.Trigger :handle="demoPreviewCard" :payload="{ title: 'Trigger 2' }" href="#">
    Trigger 2
  </PreviewCard.Trigger>

  <PreviewCard.Root v-slot="{ payload }" :handle="demoPreviewCard">
    <PreviewCard.Portal>
      <PreviewCard.Positioner :side-offset="8">
        <PreviewCard.Popup>
          <span v-if="payload !== undefined">Preview card opened by {{ payload.title }}</span>
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

Controlled mode with multiple triggers

Own the open state with v-model:open on <PreviewCard.Root>. With more than one trigger, give each trigger an id and add v-model:trigger-id to <PreviewCard.Root>: each trigger publishes its own id when it opens the card, and setting triggerId yourself anchors the card to that trigger. Pass trigger-id one-way instead if you want to drive it entirely from your own state.

Explore Mars , Saturn , or Neptune .

<script setup lang="ts">
import { shallowRef } from 'vue'
import { PreviewCard } from '@shardsui/vue/preview-card'

type Planet = { name: string; gradient: string; fact: string }

const mars: Planet = {
  name: 'Mars',
  gradient: 'radial-gradient(circle at 32% 28%, #e79c7e 0%, #c0472a 45%, #712411 100%)',
  fact: 'is a cold desert world of red dust and rock.'
}

const saturn: Planet = {
  name: 'Saturn',
  gradient: 'radial-gradient(circle at 32% 28%, #f8efcd 0%, #e3ce85 45%, #a8934a 100%)',
  fact: 'is the second largest planet, a pale gas giant so light it would float, wrapped in bright rings of ice.'
}

const neptune: Planet = {
  name: 'Neptune',
  gradient: 'radial-gradient(circle at 32% 28%, #7fb0ee 0%, #2f5fc4 45%, #14306e 100%)',
  fact: 'is the farthest planet, a ball of ice with winds faster than sound.'
}

const demoPreviewCard = PreviewCard.createHandle<Planet>()

const open = shallowRef(false)
const triggerId = shallowRef<string | null>(null)

function openNeptune() {
  triggerId.value = 'trigger-neptune'
  open.value = true
}
</script>

<template>
  <div class="flex flex-wrap items-baseline justify-center gap-2">
    <p class="m-0 text-sm/6 text-balance text-gray-900">
      Explore
      <PreviewCard.Trigger
        class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
        :handle="demoPreviewCard"
        href="https://en.wikipedia.org/wiki/Mars"
        id="trigger-mars"
        :payload="mars"
      >
        Mars
      </PreviewCard.Trigger>
      ,
      <PreviewCard.Trigger
        class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
        :handle="demoPreviewCard"
        href="https://en.wikipedia.org/wiki/Saturn"
        id="trigger-saturn"
        :payload="saturn"
      >
        Saturn
      </PreviewCard.Trigger>
      , or
      <PreviewCard.Trigger
        class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
        :handle="demoPreviewCard"
        href="https://en.wikipedia.org/wiki/Neptune"
        id="trigger-neptune"
        :payload="neptune"
      >
        Neptune
      </PreviewCard.Trigger>
      .
    </p>
    <button
      type="button"
      class="font-inherit m-0 box-border flex h-8 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3 text-sm/6 font-normal text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100"
      @click="openNeptune"
    >
      Open programmatically
    </button>
  </div>

  <PreviewCard.Root
    v-slot="{ payload }"
    v-model:open="open"
    v-model:trigger-id="triggerId"
    :handle="demoPreviewCard"
  >
    <PreviewCard.Portal>
      <PreviewCard.Positioner
        :side-offset="8"
        class="h-(--positioner-height) w-(--positioner-width) max-w-(--available-width)"
      >
        <PreviewCard.Popup
          class="box-border h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg bg-gray-50 shadow-lg outline-1 outline-gray-200 transition-[scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <PreviewCard.Arrow
            class="flex data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
              <path
                d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
                class="fill-gray-50"
              />
              <path
                d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
                class="fill-gray-200"
              />
            </svg>
          </PreviewCard.Arrow>

          <div v-if="payload" class="box-border flex w-56 flex-col items-center gap-2 p-2">
            <div
              class="size-36 rounded-full"
              :style="{ background: payload.gradient }"
              aria-hidden="true"
            ></div>
            <p class="m-0 self-stretch text-sm/5 text-pretty text-gray-900">
              <strong>{{ payload.name }}</strong>
              {{ payload.fact }}
            </p>
          </div>
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

Animating the Preview Card

When a single card hops between triggers, it can slide across rather than pop in and out. Position, size, and contents each animate on their own.

Position and Size

The Positioner carries the card's position: transition its left, right, top, and bottom. The Popup carries its size, so transition width and height there.

Content

The contents can cross-fade too when triggers show different previews. Wrap them in <PreviewCard.Viewport>, which detects the trigger change and sets a data-activation-direction attribute marking where the new trigger sits relative to the last — a horizontal and a vertical token separated by a space, e.g. right down; either can be empty. Match a single token with the ~= attribute selector, such as [data-activation-direction~='right'].

While a transition runs, the viewport holds both the old and new contents, each in its own wrapper:

  • data-current: the incoming content, or the only content when nothing is transitioning.
  • data-previous: the outgoing content during a transition.
<script setup lang="ts">
import { PreviewCard } from '@shardsui/vue/preview-card'

type Planet = { name: string; gradient: string; fact: string }

const mars: Planet = {
  name: 'Mars',
  gradient: 'radial-gradient(circle at 32% 28%, #e79c7e 0%, #c0472a 45%, #712411 100%)',
  fact: 'is a cold desert world of red dust and rock.'
}

const saturn: Planet = {
  name: 'Saturn',
  gradient: 'radial-gradient(circle at 32% 28%, #f8efcd 0%, #e3ce85 45%, #a8934a 100%)',
  fact: 'is the second largest planet, a pale gas giant so light it would float, wrapped in bright rings of ice.'
}

const neptune: Planet = {
  name: 'Neptune',
  gradient: 'radial-gradient(circle at 32% 28%, #7fb0ee 0%, #2f5fc4 45%, #14306e 100%)',
  fact: 'is the farthest planet, a ball of ice with winds faster than sound.'
}

const demoPreviewCard = PreviewCard.createHandle<Planet>()
</script>

<template>
  <p class="m-0 text-sm/6 text-balance text-gray-900">
    Explore
    <PreviewCard.Trigger
      class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
      :handle="demoPreviewCard"
      href="https://en.wikipedia.org/wiki/Mars"
      :payload="mars"
    >
      Mars
    </PreviewCard.Trigger>
    ,
    <PreviewCard.Trigger
      class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
      :handle="demoPreviewCard"
      href="https://en.wikipedia.org/wiki/Saturn"
      :payload="saturn"
    >
      Saturn
    </PreviewCard.Trigger>
    , or
    <PreviewCard.Trigger
      class="text-gray-950 underline decoration-gray-950/60 decoration-1 underline-offset-2 outline-0 hover:decoration-gray-950 focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-gray-950 data-popup-open:decoration-gray-950"
      :handle="demoPreviewCard"
      href="https://en.wikipedia.org/wiki/Neptune"
      :payload="neptune"
    >
      Neptune
    </PreviewCard.Trigger>
    .
  </p>

  <PreviewCard.Root v-slot="{ payload }" :handle="demoPreviewCard">
    <PreviewCard.Portal>
      <PreviewCard.Positioner
        :side-offset="8"
        class="h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] duration-350 ease-out-quint"
      >
        <PreviewCard.Popup
          class="relative box-border h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg bg-gray-50 shadow-lg outline-1 outline-gray-200 transition-[width,height,opacity,transform] duration-350 ease-out-quint data-ending-style:transform-[scale(0.98)] data-ending-style:opacity-0 data-starting-style:transform-[scale(0.98)] data-starting-style:opacity-0"
        >
          <PreviewCard.Arrow
            class="flex data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
          >
            <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
              <path
                d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
                class="fill-gray-50"
              />
              <path
                d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
                class="fill-gray-200"
              />
            </svg>
          </PreviewCard.Arrow>

          <PreviewCard.Viewport
            class="relative size-full overflow-clip **:data-current:w-(--popup-width) **:data-current:translate-x-0 **:data-current:opacity-100 **:data-current:transition-[translate,opacity] **:data-current:duration-[350ms,175ms] **:data-current:ease-out-quint **:data-previous:w-(--popup-width) **:data-previous:translate-x-0 **:data-previous:opacity-100 **:data-previous:transition-[translate,opacity] **:data-previous:duration-[350ms,175ms] **:data-previous:ease-out-quint data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-1/3 data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-1/3 data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-1/3 data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-1/3 data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0"
          >
            <div v-if="payload" class="box-border flex w-56 flex-col items-center gap-2 p-2">
              <div
                class="size-36 rounded-full"
                :style="{ background: payload.gradient }"
                aria-hidden="true"
              ></div>
              <p class="m-0 self-stretch text-sm/5 text-pretty text-gray-900">
                <strong>{{ payload.name }}</strong>
                {{ payload.fact }}
              </p>
            </div>
          </PreviewCard.Viewport>
        </PreviewCard.Popup>
      </PreviewCard.Positioner>
    </PreviewCard.Portal>
  </PreviewCard.Root>
</template>

API reference

Root

Groups all parts of the preview card. Doesn't render its own HTML element.

PropTypeDefault

Trigger

A link that opens the preview card. Renders an <a> element.

PropTypeDefault
AttributeDescription
data-popup-openPresent when the corresponding preview card is open.

Backdrop

An overlay displayed beneath the popup. It never receives pointer events, so hovering the page through it still works. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the preview card is open.
data-closedPresent when the preview card is closed.
data-starting-stylePresent when the backdrop is animating in.
data-ending-stylePresent when the backdrop is animating out.

Portal

A portal that moves the popup out to <body>, clear of ancestor clipping and stacking. Renders a <div> element.

PropTypeDefault

Positioner

Positions the popup against the trigger. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-anchor-hiddenPresent when the anchor is hidden.
data-instantPresent when animations should be instant.
CSS VariableDescription
--available-widthAvailable width between the anchor and the viewport edge.
--available-heightAvailable height between the anchor and the viewport edge.
--anchor-widthWidth of the anchor element.
--anchor-heightHeight of the anchor element.
--transform-originTransform origin for scale animations.
--positioner-widthThe positioner element's width. Set while a <PreviewCard.Viewport> morphs content.
--positioner-heightThe positioner element's height. Set while a <PreviewCard.Viewport> morphs content.

Popup

A container for the preview card contents. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the preview card is open.
data-closedPresent when the preview card is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-starting-stylePresent when the preview card is animating in.
data-ending-stylePresent when the preview card is animating out.
data-instantPresent when animations should be instant.
CSS VariableDescription
--popup-widthThe popup element's width. Set while a <PreviewCard.Viewport> morphs content.
--popup-heightThe popup element's height. Set while a <PreviewCard.Viewport> morphs content.

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> element.

PropTypeDefault
AttributeDescription
data-activation-directionDirection the popup was activated from. A horizontal and a vertical token separated by a space, e.g. right down; either can be empty, so match one with [data-activation-direction~='right'].
data-transitioningPresent while a content transition is in progress.
data-currentApplied to the wrapper of the current content.
data-previousApplied to the wrapper of the outgoing content, present only during a transition.
data-instantPresent when animations should be instant. Value is 'dismiss' or 'focus'.
data-starting-styleApplied to the data-current wrapper while the incoming content is animating in.
data-ending-styleApplied to the data-previous wrapper while the outgoing content is animating out.
CSS VariableDescription
--popup-widthWidth of the outgoing popup content, set on the data-previous container. Use it to freeze the popup size while the content morphs.
--popup-heightHeight of the outgoing popup content, set on the data-previous container.

Set width: var(--positioner-width) and height: var(--positioner-height) on the Positioner so its box is frozen to the measured size during the transition; otherwise content-driven resizing can make the popup thrash or flip to another side.

Arrow

Displays an element positioned against the anchor. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-uncenteredPresent when the arrow cannot be centered.

Handle

Connects a <PreviewCard.Root> with detached <PreviewCard.Trigger> components, and controls the preview card imperatively. Pass a type argument to type the payload.

const previewCard = PreviewCard.createHandle<Payload>()
MemberType