React Native Avatar
An avatar component for React Native with image support, fallback initials, status indicator, size variants, and group stacking.
Expo Snack
Code
import React, { useState } from "react";
import {
View,
Text,
Image,
StyleSheet,
type ImageSourcePropType,
type ViewStyle,
type TextStyle,
} from "react-native";
// --- Types ---
type AvatarSize = "sm" | "md" | "lg" | "xl";
type StatusType = "online" | "offline" | "busy";
interface AvatarProps {
src?: string;
name: string;
size?: AvatarSize;
status?: StatusType;
color?: string;
}
interface AvatarGroupProps {
avatars: AvatarProps[];
max?: number;
size?: AvatarSize;
}
// --- Constants ---
const SIZES: Record<AvatarSize, number> = {
sm: 32,
md: 40,
lg: 48,
xl: 64,
};
const FONT_SIZES: Record<AvatarSize, number> = {
sm: 12,
md: 15,
lg: 18,
xl: 24,
};
const STATUS_SIZES: Record<AvatarSize, number> = {
sm: 8,
md: 10,
lg: 12,
xl: 16,
};
const STATUS_COLORS: Record<StatusType, string> = {
online: "#22c55e",
offline: "#94a3b8",
busy: "#ef4444",
};
// --- Helpers ---
function getInitials(name: string): string {
const parts = name.trim().split(/\s+/);
const first = parts[0]?.[0] ?? "";
const second = parts[1]?.[0] ?? "";
return (first + second).toUpperCase();
}
// --- Avatar ---
function Avatar({ src, name, size = "md", status, color = "#6366f1" }: AvatarProps) {
const [imgError, setImgError] = useState(false);
const dim = SIZES[size];
const fontSize = FONT_SIZES[size];
const statusDim = STATUS_SIZES[size];
const showImage = !!src && !imgError;
return (
<View style={[styles.avatarWrapper, { width: dim, height: dim }]}>
{showImage ? (
<Image
source={{ uri: src }}
style={[styles.image, { width: dim, height: dim, borderRadius: dim / 2 }]}
onError={() => setImgError(true)}
/>
) : (
<View
style={[
styles.initialsContainer,
{
width: dim,
height: dim,
borderRadius: dim / 2,
backgroundColor: color,
},
]}
>
<Text style={[styles.initialsText, { fontSize }]}>{getInitials(name)}</Text>
</View>
)}
{status && (
<View
style={[
styles.statusDot,
{
width: statusDim,
height: statusDim,
borderRadius: statusDim / 2,
backgroundColor: STATUS_COLORS[status],
borderWidth: statusDim > 10 ? 2.5 : 2,
},
]}
/>
)}
</View>
);
}
// --- AvatarGroup ---
function AvatarGroup({ avatars, max = 3, size = "md" }: AvatarGroupProps) {
const visible = avatars.slice(0, max);
const overflow = avatars.length - max;
const dim = SIZES[size];
const fontSize = FONT_SIZES[size] - 2;
const overlap = -(dim * 0.3);
return (
<View style={styles.groupContainer}>
{visible.map((avatar, i) => (
<View
key={i}
style={[
styles.groupItem,
{ marginLeft: i === 0 ? 0 : overlap, zIndex: visible.length - i },
]}
>
<View style={[styles.groupRing, { borderRadius: (dim + 4) / 2 }]}>
<Avatar {...avatar} size={size} />
</View>
</View>
))}
{overflow > 0 && (
<View style={[styles.groupItem, { marginLeft: overlap, zIndex: 0 }]}>
<View
style={[
styles.overflowCircle,
{
width: dim,
height: dim,
borderRadius: dim / 2,
},
]}
>
<Text style={[styles.overflowText, { fontSize }]}>+{overflow}</Text>
</View>
</View>
)}
</View>
);
}
// --- Demo App ---
export default function App() {
const groupAvatars: AvatarProps[] = [
{ name: "Alice Morgan", src: "https://picsum.photos/seed/a1/200" },
{ name: "Bob Chen", src: "https://picsum.photos/seed/b2/200" },
{ name: "Carol Davis", src: "https://picsum.photos/seed/c3/200" },
{ name: "Dan Wilson", color: "#8b5cf6" },
{ name: "Eve Taylor", color: "#ec4899" },
];
return (
<View style={styles.screen}>
<Text style={styles.heading}>Avatar</Text>
{/* Size variants with initials */}
<Text style={styles.label}>Sizes</Text>
<View style={styles.row}>
<Avatar name="Amy Lin" size="sm" color="#8b5cf6" />
<Avatar name="Amy Lin" size="md" color="#8b5cf6" />
<Avatar name="Amy Lin" size="lg" color="#8b5cf6" />
<Avatar name="Amy Lin" size="xl" color="#8b5cf6" />
</View>
{/* Image avatar */}
<Text style={styles.label}>With Image</Text>
<View style={styles.row}>
<Avatar name="John Doe" src="https://picsum.photos/seed/jd/200" size="lg" />
<Avatar name="Jane Smith" src="https://picsum.photos/seed/js/200" size="lg" />
</View>
{/* Status indicators */}
<Text style={styles.label}>Status</Text>
<View style={styles.row}>
<Avatar name="Online User" size="lg" color="#6366f1" status="online" />
<Avatar name="Offline User" size="lg" color="#6366f1" status="offline" />
<Avatar name="Busy User" size="lg" color="#6366f1" status="busy" />
</View>
{/* Avatar group */}
<Text style={styles.label}>Group</Text>
<AvatarGroup avatars={groupAvatars} max={3} size="lg" />
</View>
);
}
// --- Styles ---
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: "#0f172a",
padding: 24,
paddingTop: 60,
},
heading: {
color: "#f8fafc",
fontSize: 24,
fontWeight: "700",
marginBottom: 24,
},
label: {
color: "#94a3b8",
fontSize: 13,
fontWeight: "600",
textTransform: "uppercase",
letterSpacing: 1,
marginBottom: 10,
marginTop: 20,
},
row: {
flexDirection: "row",
alignItems: "center",
gap: 12,
},
avatarWrapper: {
position: "relative",
},
image: {
resizeMode: "cover",
},
initialsContainer: {
alignItems: "center",
justifyContent: "center",
},
initialsText: {
color: "#ffffff",
fontWeight: "700",
},
statusDot: {
position: "absolute",
bottom: 0,
right: 0,
borderColor: "#0f172a",
},
groupContainer: {
flexDirection: "row",
alignItems: "center",
},
groupItem: {
position: "relative",
},
groupRing: {
padding: 2,
backgroundColor: "#0f172a",
},
overflowCircle: {
backgroundColor: "#334155",
alignItems: "center",
justifyContent: "center",
},
overflowText: {
color: "#f8fafc",
fontWeight: "700",
},
});React Native Avatar
A versatile avatar component that displays user profile images with graceful fallback to generated initials. Includes a status indicator dot, multiple size presets, and an AvatarGroup for stacking multiple avatars with an overflow counter.
Sizes
Four size variants are available — sm (32px), md (40px), lg (48px), and xl (64px). Each size automatically scales the initials font size and the status indicator dot proportionally.
Props
Avatar
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URI to display |
name | string | — | Full name used to generate fallback initials |
size | "sm" | "md" | "lg" | "xl" | "md" | Controls the avatar dimensions |
status | "online" | "offline" | "busy" | — | Shows a colored status indicator dot |
color | string | "#6366f1" | Background color when showing initials |
AvatarGroup
| Prop | Type | Default | Description |
|---|---|---|---|
avatars | AvatarProps[] | — | Array of avatar prop objects |
max | number | 3 | Maximum avatars to display before showing overflow |
Usage
Import the Avatar and AvatarGroup components and pass the relevant props. When a src is provided and loads successfully, the image is displayed. If no src is given or the image fails to load, initials are extracted from the name prop and rendered on a colored background.
How it works
The component extracts initials by splitting the name string on spaces and taking the first character of the first two words. The status dot is absolutely positioned at the bottom-right corner of the avatar circle. AvatarGroup renders avatars with negative horizontal margins so they overlap, and appends a “+N” circle when the avatar count exceeds the max threshold.