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