Cards

Apple Invites

A stacked carousel of Apple-style invite cards that auto-advances with 3D-like slide transitions.

Apple Invites renders a small stack of overlapping cards that cycle through a list of events. At any time it shows up to three slots (left, center, right) with scaled/rotated transforms for the side cards and a blur-masked gradient underlay for depth. It auto-advances at interval ms (when mounted client-side), can be controlled via activeIndex, and emits update:activeIndex when the internal page changes. Responsive card sizing is supported via cardWidth/cardHeight/aspectRatio.

Usage

Pass an array of events. Each event needs an id and at least location; title, subtitle, badge, image or backgroundClassName, and participants (array of { avatar }) are optional. The carousel tracks its own page when activeIndex is undefined.

Team Dinner
Social
Participant 1

Team Dinner

Celebrating a great quarter

New York, NY

Product Launch
Invite

Product Launch

Announcing our latest release

Online

Design Meetup
Group
Participant 1Participant 2

Design Meetup

Share ideas with the community

San Francisco, CA

<template>
  <div class="h-64 w-full max-w-sm">
    <MAppleInvites
      :events="events"
      :interval="2500"
    />
  </div>
</template>

<script setup lang="ts">
const events = [
  {
    id: 1,
    title: 'Product Launch',
    subtitle: 'Announcing our latest release',
    location: 'Online',
    badge: 'Invite',
    image: 'https://images.unsplash.com/photo-1529333166437-7750a6dd5a70?w=800&h=500&fit=crop',
  },
  {
    id: 2,
    title: 'Design Meetup',
    subtitle: 'Share ideas with the community',
    location: 'San Francisco, CA',
    badge: 'Group',
    image: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=800&h=500&fit=crop',
    participants: [
      { avatar: 'https://randomuser.me/api/portraits/women/44.jpg' },
      { avatar: 'https://randomuser.me/api/portraits/men/32.jpg' },
    ],
  },
  {
    id: 3,
    title: 'Team Dinner',
    subtitle: 'Celebrating a great quarter',
    location: 'New York, NY',
    badge: 'Social',
    image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=500&fit=crop',
    participants: [
      { avatar: 'https://randomuser.me/api/portraits/women/68.jpg' },
    ],
  },
]
</script>

Customize

Team Dinner
Social

Team Dinner

Celebrating a great quarter

New York, NY

Product Launch
Invite

Product Launch

Announcing our latest release

Online

Design Meetup
Group

Design Meetup

Share ideas with the community

San Francisco, CA

Customize

Active Index
Aspect Ratio1.5625
Card Class Name
Card Height
Card Width240
Interval3000

Props

Prop Type Default Description
eventsrequiredEvent[]-Invitation cards to cycle through. Each Event has id (number), location (string), optional title, subtitle, badge, image, backgroundClassName, participants: { avatar: string }[].
activeIndexnumberundefinedControlled active card. When undefined, the component tracks internalPage and auto-advances.
intervalnumber3000Milliseconds between automatic advances. Set to <= 0 to disable auto-advance. Timer runs only in the browser (import.meta.client).
cardWidthnumber | string | ResponsiveSize240Card width. Pass ResponsiveSize to scale by viewport breakpoints (sm/md/lg/xl/2xl/base).
cardHeightnumber | string | ResponsiveSizeundefinedExplicit card height. Falls back to cardWidth / aspectRatio when undefined.
aspectRationumber1.5625Used when cardHeight omitted: height = width * aspectRatio (5:8 ratio by default).
cardClassNamestringundefinedMerged onto each card wrapper.
classstringundefinedMerged onto the outer centering wrapper.

Events

Event Payload Description
update:activeIndex(index: number) => voidEmitted when the internal page changes (auto-advance or programmatic next) while not controlled. The payload is the wrapped index.

Slots

Slot Props Description
default-No default slot is used by the component; content is rendered from events data.

UI

This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.appleInvites in app.config.ts.

Prop Type Default Description
uiobject-Per-slot classes merged over the theme defaults.
Slot Props Description
root-relative flex h-full w-full items-center justify-center
card-absolute top-1/2 left-1/2 origin-center -translate-y-1/2
cardInner-relative h-full w-full overflow-hidden rounded-3xl bg-primary
background-h-full w-full
image-h-full w-full object-cover
badgeWrap-absolute z-3
badge-flex flex-row items-center rounded-full bg-default/30 font-medium text-inverted backdrop-blur-xl
content-absolute bottom-0 z-3 w-full rounded-b-3xl text-inverted
avatars-mx-auto flex items-center justify-center
avatar-rounded-full
title-wrap-break-word text-center font-bold
subtitle-wrap-break-word text-center opacity-90
location-wrap-break-word text-center opacity-90
blurWrap-absolute inset-x-0 bottom-0 isolate z-2 h-1/2
blurLayer-absolute inset-0 overflow-hidden rounded-3xl

Accessibility

All three visible cards are absolutely positioned motion.div elements with no role or aria-label describing their state; decorative blur layers use aria-hidden not set but are non-interactive. Images use alt text (title or generic "Participant N"). The carousel advances automatically with no pause-on-hover/focus behavior declared. No keyboard controls are exposed. useReducedMotion is respected for transitions (swaps to zero-duration or reduced transitions), but focus management and ARIA live region for slide changes are not implemented. Consumers should provide their own controls/announcements if needed.

Performance

  • Uses AnimatePresence with custom="direction" and variants for center/left/right/hidden. Side cards are scaled down (0.9) and rotated (±12°) with z-index layering.
  • Responsive sizing computes metrics (font sizes, padding, avatar sizes, badge position) from resolved card width; initial size uses base or first available value, client-only re-resolves on viewport width.
  • Auto-advance uses setInterval (only import.meta.client, restarted on page/interval/events change). Clears timer on unmount.
  • Blurred underlay uses five stacked layers (BLUR_LAYERS = [1,2,3,6,12]) with backdrop-filter/mask-image for a gradient fade.
  • When prefers-reduced-motion, transitions become near-zero duration (REDUCED_TRANSITION) and glass fade is shortened; variants still apply.
  • For lists with < 3 events, the same event id may appear twice in visibleEvents (distinguished by key = id + "-" + slot) so Vue's keyed transitions don't collapse copies.
To keep the stack visually stable, ensure events has stable id values. If you need manual control, bind v-model:activeIndex (or :activeIndex + @update:activeIndex) and consider disabling interval by setting it to 0.
Copyright © 2026