Apple Invites
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
Celebrating a great quarter
New York, NY
Product Launch
Announcing our latest release
Online
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
Celebrating a great quarter
New York, NY
Product Launch
Announcing our latest release
Online
Design Meetup
Share ideas with the community
San Francisco, CA
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
eventsrequired | Event[] | - | Invitation cards to cycle through. Each Event has id (number), location (string), optional title, subtitle, badge, image, backgroundClassName, participants: { avatar: string }[]. |
activeIndex | number | undefined | Controlled active card. When undefined, the component tracks internalPage and auto-advances. |
interval | number | 3000 | Milliseconds between automatic advances. Set to <= 0 to disable auto-advance. Timer runs only in the browser (import.meta.client). |
cardWidth | number | string | ResponsiveSize | 240 | Card width. Pass ResponsiveSize to scale by viewport breakpoints (sm/md/lg/xl/2xl/base). |
cardHeight | number | string | ResponsiveSize | undefined | Explicit card height. Falls back to cardWidth / aspectRatio when undefined. |
aspectRatio | number | 1.5625 | Used when cardHeight omitted: height = width * aspectRatio (5:8 ratio by default). |
cardClassName | string | undefined | Merged onto each card wrapper. |
class | string | undefined | Merged onto the outer centering wrapper. |
Events
| Event | Payload | Description |
|---|---|---|
update:activeIndex | (index: number) => void | Emitted when the internal page changes (auto-advance or programmatic next) while not controlled. The payload is the wrapped index. |
Slots
MAppleInvites exposes no slots. The whole stack is rendered from the events
array.
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 |
|---|---|---|---|
ui | object | - | 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
AnimatePresencewithcustom="direction"andvariantsforcenter/left/right/hidden. Side cards are scaled down (0.9) and rotated (±12°) withz-indexlayering. - Responsive sizing computes
metrics(font sizes, padding, avatar sizes, badge position) from resolved card width; initial size usesbaseor first available value, client-only re-resolves on viewport width. - Auto-advance uses
setInterval(onlyimport.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]) withbackdrop-filter/mask-imagefor 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 bykey = id + "-" + slot) so Vue's keyed transitions don't collapse copies.
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.