0Pricing
React Native Academy · Lesson

Composing a Simple Profile Card

Combine View, Text, and Image to build a profile card component that shows an avatar, name, and bio, reinforcing component composition skills.

Composing a Simple Profile Card is a free React Native Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Native Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Component Composition in React Native

Composition means building big UI from small pieces. A profile card mixes a View, an Image, and Text — and once built, you can reuse it for every user.

// The goal: a reusable ProfileCard component
// that accepts props and renders a complete card UI

// Usage:
<ProfileCard
  name='Alice Johnson'
  role='Senior Developer'
  bio='Building great mobile apps with React Native.'
  avatarUri='https://i.pravatar.cc/150?img=1'
  followers={1240}
/>

Planning the Card Layout

Before coding, plan the card: avatar, name, role, bio, maybe a stats row. A column for the overall layout, a row for side-by-side bits. A quick sketch saves time.

// Conceptual structure (pseudo-code)
<View card>
  <Image avatar />
  <Text name />
  <Text role />
  <Text bio />
  <View statsRow>
    <View stat><Text number/><Text label/></View>
    <View stat><Text number/><Text label/></View>
    <View stat><Text number/><Text label/></View>
  </View>
</View>

Building the Card Container

Start with the card container View. Add borderRadius for rounded corners, a shadow for lift, and padding inside. Center the children with alignItems.

import { View, StyleSheet } from 'react-native';

export default function ProfileCard({ children }) {
  return (
    <View style={styles.card}>
      {children}
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 20,
    padding: 24,
    marginHorizontal: 20,
    marginVertical: 12,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.1,
    shadowRadius: 12,
    elevation: 5,
  },
});

Adding the Avatar Image

Add the avatar at the top: a square View with borderRadius half its size and overflow: hidden makes it a circle. Take the image as a prop to stay reusable.

import { Image, View, StyleSheet } from 'react-native';

function Avatar({ uri, size = 90 }) {
  const radius = size / 2;
  return (
    <View style={[
      styles.ring,
      { width: size + 6, height: size + 6, borderRadius: radius + 3 }
    ]}>
      <View style={[
        styles.clip,
        { width: size, height: size, borderRadius: radius }
      ]}>
        <Image source={{ uri }} style={{ width: size, height: size }} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  ring: { borderWidth: 3, borderColor: '#4f86f7', alignItems: 'center', justifyContent: 'center' },
  clip: { overflow: 'hidden', backgroundColor: '#e0e0e0' },
});

Name, Role, and Bio Text

Below the avatar, add three Text blocks: a big bold name, a smaller muted role, and a centered bio. A little marginTop between them sets a clean rhythm.

import { Text, StyleSheet } from 'react-native';

function CardInfo({ name, role, bio }) {
  return (
    <>
      <Text style={styles.name}>{name}</Text>
      <Text style={styles.role}>{role}</Text>
      <Text style={styles.bio} numberOfLines={3}>{bio}</Text>
    </>
  );
}

const styles = StyleSheet.create({
  name: { fontSize: 22, fontWeight: '700', color: '#111', marginTop: 14, textAlign: 'center' },
  role: { fontSize: 14, color: '#4f86f7', fontWeight: '500', marginTop: 4, textAlign: 'center' },
  bio: { fontSize: 14, color: '#666', lineHeight: 22, marginTop: 10, textAlign: 'center' },
});

Stat Counter Row

Many cards end with a stats row: followers and posts side by side. Use flexDirection: row with justifyContent: space-around and a top border to set it off.

import { View, Text, StyleSheet } from 'react-native';

function StatRow({ followers, following, posts }) {
  return (
    <View style={styles.row}>
      <Stat value={posts} label='Posts' />
      <Stat value={followers} label='Followers' />
      <Stat value={following} label='Following' />
    </View>
  );
}

function Stat({ value, label }) {
  return (
    <View style={styles.stat}>
      <Text style={styles.value}>{value}</Text>
      <Text style={styles.label}>{label}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', justifyContent: 'space-around', width: '100%', borderTopWidth: 1, borderTopColor: '#eee', marginTop: 20, paddingTop: 16 },
  stat: { alignItems: 'center' },
  value: { fontSize: 18, fontWeight: '700', color: '#111' },
  label: { fontSize: 12, color: '#999', marginTop: 2 },
});

Assembling the Full ProfileCard

Now combine the parts into one ProfileCard that takes props and just renders them. No internal state — pure presentation makes it easy to reuse and test.

import { View, StyleSheet } from 'react-native';
import Avatar from './Avatar';
import CardInfo from './CardInfo';
import StatRow from './StatRow';

export default function ProfileCard({
  name, role, bio, avatarUri, followers, following, posts
}) {
  return (
    <View style={styles.card}>
      <Avatar uri={avatarUri} size={90} />
      <CardInfo name={name} role={role} bio={bio} />
      <StatRow followers={followers} following={following} posts={posts} />
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 20,
    padding: 24,
    marginHorizontal: 20,
    alignItems: 'center',
    elevation: 5,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.1,
    shadowRadius: 12,
  },
});

Rendering Multiple Cards in a List

To show many cards, map over an array of users. For long lists reach for FlatList; for a few, ScrollView is fine. Give each card a unique key.

import { ScrollView } from 'react-native';
import ProfileCard from './ProfileCard';

const users = [
  { id: '1', name: 'Alice', role: 'Designer', bio: 'Crafting beautiful UIs.', avatarUri: 'https://i.pravatar.cc/150?img=1', followers: 980, following: 210, posts: 45 },
  { id: '2', name: 'Bob', role: 'Developer', bio: 'Loves React Native and coffee.', avatarUri: 'https://i.pravatar.cc/150?img=2', followers: 1420, following: 380, posts: 73 },
];

export default function App() {
  return (
    <ScrollView style={{ backgroundColor: '#f5f5f5' }}>
      {users.map(user => (
        <ProfileCard key={user.id} {...user} />
      ))}
    </ScrollView>
  );
}

Adding a Follow Button

Make it interactive with a Follow button using TouchableOpacity. Toggle the label and color, but keep the real API call in the parent via a callback.

import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import { useState } from 'react';

export default function FollowButton({ onPress }) {
  const [following, setFollowing] = useState(false);

  function handlePress() {
    setFollowing(prev => !prev);
    onPress && onPress(!following);
  }

  return (
    <TouchableOpacity
      style={[styles.btn, following && styles.btnActive]}
      onPress={handlePress}
    >
      <Text style={[styles.label, following && styles.labelActive]}>
        {following ? 'Following' : 'Follow'}
      </Text>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  btn: { borderWidth: 1.5, borderColor: '#4f86f7', borderRadius: 20, paddingHorizontal: 24, paddingVertical: 8, marginTop: 16 },
  btnActive: { backgroundColor: '#4f86f7' },
  label: { color: '#4f86f7', fontWeight: '600' },
  labelActive: { color: '#fff' },
});

Adding Accessibility to the Card

Make the card accessible: set accessible on the outer View with a label that sums it up, so screen readers announce the whole card as one clear unit.

import { View, Text, StyleSheet } from 'react-native';

export default function AccessibleProfileCard({ name, role, followers }) {
  const label = `${name}, ${role}, ${followers} followers`;

  return (
    <View
      style={styles.card}
      accessible
      accessibilityLabel={label}
      accessibilityRole='summary'
    >
      <Text style={styles.name}>{name}</Text>
      <Text style={styles.role}>{role}</Text>
      <Text style={styles.followers}>{followers} followers</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 16, borderRadius: 12, backgroundColor: '#fff', elevation: 3 },
  name: { fontSize: 20, fontWeight: 'bold' },
  role: { color: '#666', marginTop: 4 },
  followers: { color: '#4f86f7', marginTop: 8 },
});

Skeleton Loading State

While data loads, show a skeleton — grey pulsing boxes shaped like the card. It feels more polished than a spinner and hints at what is coming.

import { View, StyleSheet } from 'react-native';

export default function ProfileCardSkeleton() {
  return (
    <View style={styles.card}>
      {/* Avatar placeholder */}
      <View style={styles.avatarSkeleton} />
      {/* Name placeholder */}
      <View style={styles.nameSkeleton} />
      {/* Role placeholder */}
      <View style={styles.roleSkeleton} />
      {/* Bio placeholder lines */}
      <View style={[styles.bioSkeleton, { width: '100%' }]} />
      <View style={[styles.bioSkeleton, { width: '80%' }]} />
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 24, borderRadius: 20, backgroundColor: '#fff', alignItems: 'center', margin: 16, elevation: 3 },
  avatarSkeleton: { width: 90, height: 90, borderRadius: 45, backgroundColor: '#e0e0e0' },
  nameSkeleton: { width: 140, height: 22, borderRadius: 4, backgroundColor: '#e0e0e0', marginTop: 14 },
  roleSkeleton: { width: 100, height: 16, borderRadius: 4, backgroundColor: '#e0e0e0', marginTop: 8 },
  bioSkeleton: { height: 14, borderRadius: 4, backgroundColor: '#e0e0e0', marginTop: 8 },
});

Quick Check

Quick check! See how your ProfileCard skills stack up. 🎉

Lesson Recap

Awesome — you composed a real card! You combined View, Text, and Image, used props to reuse it, and added skeletons for polish. Next up: StyleSheet and Flexbox.

Frequently asked questions

Is the “Composing a Simple Profile Card” lesson free?

Yes — the full text of “Composing a Simple Profile Card” is free to read here on the web, and the React Native Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Native Academy course, upgrade to CoddyKit PRO.

What will I learn in “Composing a Simple Profile Card”?

Combine View, Text, and Image to build a profile card component that shows an avatar, name, and bio, reinforcing component composition skills. You practise React Native Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start React Native Academy?

No prior experience is required. React Native Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Composing a Simple Profile Card” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this React Native Academy lesson?

Yes. Every React Native Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The View Component as a Container
  2. Displaying Text and Styling Fonts
  3. Showing Images from Local and Remote Sources
  4. Composing a Simple Profile Card
← Back to React Native Academy