---
title: "Apple Invites"
description: "A stacked carousel of Apple-style invite cards that auto-advances with 3D-like slide transitions."
canonical_url: "https://nuxt-motion-block.pages.dev/components/cards/apple-invites"
---
# 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.

<code-preview label="Preview">
<div className="flex,justify-center,w-full,h-full">
<component-example name="apple-invites-basic">



</component-example>
</div>

<template v-slot:code="">

```vue
<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>
```

</template>
</code-preview>

## Customize

<code-preview label="Preview">
<div className="w-full">
<component-example name="apple-invites-custom">



</component-example>
</div>

<template v-slot:code="">
<playground-code name="AppleInvites">



</playground-code>
</template>
</code-preview>

## Props

<component-props>
<component-prop name="events" type="Event[]" :required="true">

Invitation cards to cycle through. Each `Event` has `id` (number), `location` (string), optional `title`, `subtitle`, `badge`, `image`, `backgroundClassName`, `participants: { avatar: string }[]`.

</component-prop>

<component-prop name="activeIndex" type="number" default="undefined">

Controlled active card. When undefined, the component tracks `internalPage` and auto-advances.

</component-prop>

<component-prop name="interval" type="number" default="3000">

Milliseconds between automatic advances. Set to `<= 0` to disable auto-advance. Timer runs only in the browser (`import.meta.client`).

</component-prop>

<component-prop name="cardWidth" type="number | string | ResponsiveSize" default="240">

Card width. Pass `ResponsiveSize` to scale by viewport breakpoints (`sm/md/lg/xl/2xl/base`).

</component-prop>

<component-prop name="cardHeight" type="number | string | ResponsiveSize" default="undefined">

Explicit card height. Falls back to `cardWidth / aspectRatio` when undefined.

</component-prop>

<component-prop name="aspectRatio" type="number" default="1.5625">

Used when `cardHeight` omitted: `height = width * aspectRatio` (5:8 ratio by default).

</component-prop>

<component-prop name="cardClassName" type="string" default="undefined">

Merged onto each card wrapper.

</component-prop>

<component-prop name="class" type="string" default="undefined">

Merged onto the outer centering wrapper.

</component-prop>
</component-props>

## Events

<component-events>
<component-event name="update:activeIndex" type="(index: number) => void">

Emitted when the internal page changes (auto-advance or programmatic next) while not controlled. The payload is the wrapped index.

</component-event>
</component-events>

## Slots

<component-slots>
<component-slot-row name="default">

No default slot is used by the component; content is rendered from `events` data.

</component-slot-row>
</component-slots>

## UI

This component's theme exposes the slots below. Override any of them per instance with the [`ui` prop](https://nuxt-motion-block.pages.dev/getting-started/theming), or app-wide under `ui.appleInvites` in `app.config.ts`.

<component-props>
<component-prop name="ui" type="object">

Per-slot classes merged over the theme defaults.

</component-prop>
</component-props>

<component-slots>
<component-slot-row name="root">

`relative flex h-full w-full items-center justify-center`

</component-slot-row>

<component-slot-row name="card">

`absolute top-1/2 left-1/2 origin-center -translate-y-1/2`

</component-slot-row>

<component-slot-row name="cardInner">

`relative h-full w-full overflow-hidden rounded-3xl bg-primary`

</component-slot-row>

<component-slot-row name="background">

`h-full w-full`

</component-slot-row>

<component-slot-row name="image">

`h-full w-full object-cover`

</component-slot-row>

<component-slot-row name="badgeWrap">

`absolute z-3`

</component-slot-row>

<component-slot-row name="badge">

`flex flex-row items-center rounded-full bg-default/30 font-medium text-inverted backdrop-blur-xl`

</component-slot-row>

<component-slot-row name="content">

`absolute bottom-0 z-3 w-full rounded-b-3xl text-inverted`

</component-slot-row>

<component-slot-row name="avatars">

`mx-auto flex items-center justify-center`

</component-slot-row>

<component-slot-row name="avatar">

`rounded-full`

</component-slot-row>

<component-slot-row name="title">

`wrap-break-word text-center font-bold`

</component-slot-row>

<component-slot-row name="subtitle">

`wrap-break-word text-center opacity-90`

</component-slot-row>

<component-slot-row name="location">

`wrap-break-word text-center opacity-90`

</component-slot-row>

<component-slot-row name="blurWrap">

`absolute inset-x-0 bottom-0 isolate z-2 h-1/2`

</component-slot-row>

<component-slot-row name="blurLayer">

`absolute inset-0 overflow-hidden rounded-3xl`

</component-slot-row>
</component-slots>

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

<tip>

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

</tip>


## Sitemap

See the full [sitemap](https://nuxt-motion-block.pages.dev/sitemap.md) for all pages.
