About this animation
This React Native avatar stack presents collaborators as gradient initials, a compact overflow count, and an expandable glass roster. Avatars move between their stack position and named roster rows while decorative cursors reinforce the shared-document setting. Supply a stable pool of up to eight members and initial active IDs, then use join, leave, or setMembers to reflect your own presence events. Four avatars fit the collapsed stack; additional active members contribute to overflow. This is a collaboration composition with callbacks, not a photo-avatar uploader or a presence backend.
When to use it
- Show active collaborators in a document.
- Expand a compact stack into a named roster.
- Visualize join and leave events from your app.
A collaborator avatar stack with a rolling overflow count and expanding roster, with configurable members, title, fonts, and accent color.
Why this one
Presence feels like a team working together rather than a static row of profile pictures. The same avatar travels from the header to its roster row; named cursors and tinted editing lines connect each person to the work. Gradient initials keep the art consistent without photos.
How it works
- A shared ordered array stores active identities. Each mounted avatar derives its rank, visibility, and arrival from that array, so leaving people remain available for an exit transition.
- One open progress drives the glass surface, overflow visibility, and roster. The material reaches full opacity before staggered avatar flights start, and remains visible until they return. The roster grows from the stack, with interruptible, nearly critical springs. Names fade in near their rows rather than crossing the document as readable text.
- The fifth avatar fades into a fixed overflow badge. Its digit translates and fades by distance inside a measured one-line window.
- Two document cursors evaluate cubic Bézier control points on the UI thread inside a measured, clipped text region. Pass
cursorAreafrom your body’sonLayoutto exclude headings. The document fades to 40% opacity while the roster is open, without a dimming overlay. - Imperative methods trigger semantic events. React updates only the active identity snapshot and announcement text, never animation frames.
- A BlurTargetView supplies the glass backdrop on Android. Reduced motion replaces travel with 200 ms fades, fixes the cursors, disables blur, and updates the digit without rolling.
Install
npx expo install @expo/vector-icons expo-blur expo-linear-gradient react-native-reanimated react-native-workletsUse the SDK-compatible versions selected by Expo. No additional assets, backend, or Skia setup are required. Copy this folder without demo/ into your project. Icons: Feather, the kit-approved Lucide predecessor; bundled by Expo. The copied Lucide paths in icons.ts are available for hosts using a path renderer. Icons: Lucide (ISC).
Usage
import { PresenceAvatarStack } from './presence-avatar-stack';
const members = [
{
id: 'editor', name: 'Editor', initials: 'ED',
colors: ['#A18CFF', '#6854CF'] as const,
},
];
<PresenceAvatarStack members={members} initialMemberIds={['editor']} />;import { useRef } from 'react';
import { Text } from 'react-native';
import {
PresenceAvatarStack,
type PresenceAvatarStackRef,
} from './presence-avatar-stack';
const presence = useRef<PresenceAvatarStackRef>(null);
<PresenceAvatarStack
ref={presence}
members={members}
title="Project brief"
accentColor="#6366F1"
colorScheme="light"
springDamping={30}
blurIntensity={55}
onPresenceChange={(ids) => savePresence(ids)}
onShare={() => openShareSheet()}
>
<Text>Your document content</Text>
</PresenceAvatarStack>;
// In a socket handler or a button callback:
presence.current?.join('editor');
presence.current?.expand();savePresence and openShareSheet stand for your application callbacks. Keep the member pool and its ID ordering stable while mounted. Add known IDs with join; unknown IDs and duplicate joins are ignored. The component supports up to eight people and four collapsed avatars. It requires a bounded container; the root uses flex: 1 and measures itself with onLayout.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
members | readonly PresenceMember[] | Required | Available people; stable, unique IDs, at most eight. |
initialMemberIds | readonly string[] | [] | Active people on mount, front to back. |
title | string | Shared document | Header title. |
children | ReactNode | — | Document body. |
cursorArea | { top: number; height: number } | { top: 160, height: 180 } | Cursor region in document-body coordinates, clamped to its measured height. |
fontFamily | string | System | Interface and initials font. |
monoFontFamily | string | System | Overflow digit font. |
accentColor | string | #6366F1 | Share and summary accent. |
colorScheme | light / dark / auto | auto | Appearance. |
blurIntensity | number | 60 | Glass strength, clamped to 0–100. |
springDamping | number | 28 | Damping, clamped to at least 28. |
accessibilityLabel | string | Show collaborators | Presence toggle label. |
onPresenceChange | (ids: string[]) => void | — | Active identities changed. |
onExpandedChange | (expanded: boolean) => void | — | Roster toggled. |
onInteraction | () => void | — | Any user touch, useful for stopping autoplay. |
onShare | () => void | — | Share action; button omitted without it. |
style | StyleProp<ViewStyle> | — | Root overrides. |
testID | string | — | Automation identifier. |
PresenceMember: id, name, initials, gradient colors, and optional activity (defaults to Editing · Section 2).
Ref methods: join(id), leave(id), setMembers(ids), expand(), collapse().
Demo timeline
Each 7-second cycle starts with four people. Three join at 450, 1100, and 1750 ms; one leaves at 2800 ms. The roster opens at 3500 ms and closes at 5600 ms. Touching anywhere stops the cycle. “Invite the team” offers a manual presence sequence. A second control switches appearance.
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, expo-blur ~58.0.5, expo-linear-gradient ~58.0.3, @expo/vector-icons ^15.0.2. Compatible with Expo Go. Android 12+ uses the efficient blur implementation; older Android versions use the translucent fallback. No Skia dependency.
Accessibility
All interactive targets are at least 44 pt, have button roles and labels, and expose the expanded state. Tap opens or closes the stack; tapping outside closes the roster. Important presence and expansion changes are announced; the summary is a polite live region. The visible roster exposes active names and activities as grouped accessible rows; inactive visual duplicates are hidden from screen readers. Reduced motion removes flights, scale changes, rolling figures, cursor motion, and blur in favor of 200 ms fades.
Implementation notes
- Readability: gradient initials have white 13 pt text; roster labels use dark text on light glass and light text on dark glass. Material precedes avatar travel and remains through collapse; document opacity is 40% at full expansion. Cursor tags are clipped below the heading to the demo’s measured text region. Document lines are intentionally decorative.
- Viewer close button: the brand stays left, Share is inset, and controls are at the bottom. No important content occupies the upper-right corner.
- 390×844 and 320×694: layout uses the measured container. Below 800 pt, the demo shortens its decorative body and omits the milestone card to protect the footer. The header keeps the avatar stack and Share separated. Cursor bounds update from the text region’s layout at both sizes.
- First three seconds: three spring arrivals and the rolling +1, +2, +3 badge are scheduled before 2 seconds.
- Effects: cursor highlights tint document lines; glass uses the actual backdrop. No opaque moving block simulates a shine.
FAQ
- Can I use photos or unlimited people in this avatar stack?
- The documented member model uses initials and gradient colors, with a maximum of eight people and four collapsed avatars. It does not expose a photo-upload API or an unlimited roster.
- Does this component provide real-time presence?
- Your application supplies presence events. Keep member IDs and pool ordering stable while mounted, and call join, leave, or setMembers. Unknown IDs and duplicate joins are ignored. No backend or network synchronization is included.
- How does the roster support accessibility?
- The stack has a labeled button and expanded state; visible roster rows group names and activities. Presence changes are announced. Reduced Motion removes avatar flights, rolling digits, cursor motion, and blur in favor of short fades.



